refactor(animation): 移除旧动画系统并添加Tween动画支持

重构动画系统,删除旧动画组件(AnimationClip, AnimatedSprite等),添加新的Tween动画系统。主要变更包括:
1. 删除旧的动画相关文件(animation_clip, animation_frame等)
2. 实现新的Tween动画系统,支持多种缓动函数
3. 更新flappy_bird示例使用新的Tween系统
4. 调整include路径和xmake配置
5. 添加输入系统对控制器和鼠标滚轮的支持
This commit is contained in:
ChestnutYueyue 2026-02-25 21:22:35 +08:00
parent 5039b1d9fc
commit a6c1f66fff
45 changed files with 1367 additions and 3941 deletions

View File

@ -10,7 +10,7 @@ local example_dir = os.scriptdir()
target("collision_demo") target("collision_demo")
set_kind("binary") set_kind("binary")
add_files("main.cpp") add_files("main.cpp")
add_includedirs("../../Extra2D/include") add_includedirs("../../include")
add_deps("extra2d") add_deps("extra2d")
-- 使用与主项目相同的平台配置 -- 使用与主项目相同的平台配置

View File

@ -8,6 +8,7 @@
#include "Number.h" #include "Number.h"
#include "ResLoader.h" #include "ResLoader.h"
#include "StartScene.h" #include "StartScene.h"
#include <animation/tween.h>
namespace flappybird { namespace flappybird {
@ -25,7 +26,8 @@ void GameOverLayer::onEnter() {
float screenHeight = GAME_HEIGHT; float screenHeight = GAME_HEIGHT;
// 整体居中x 坐标相对于屏幕中心) // 整体居中x 坐标相对于屏幕中心)
setPosition(extra2d::Vec2(screenWidth / 2.0f, screenHeight)); // 初始位置在屏幕底部,然后向上滑出
setPosition(extra2d::Vec2(screenWidth / 2.0f, screenHeight + 200.0f));
// 显示 "Game Over" 文字y=120从顶部开始 // 显示 "Game Over" 文字y=120从顶部开始
auto gameOverFrame = ResLoader::getKeyFrame("text_game_over"); auto gameOverFrame = ResLoader::getKeyFrame("text_game_over");
@ -43,14 +45,23 @@ void GameOverLayer::onEnter() {
// 初始化按钮 // 初始化按钮
initButtons(); initButtons();
// 动画完成,启用按钮 // 向上滑出动画
animationDone_ = true; extra2d::TweenOptions opts;
if (restartBtn_) opts.easing = extra2d::TweenEasing::BackOut;
restartBtn_->setEnabled(true); tween()
if (menuBtn_) .to(0.5f,
menuBtn_->setEnabled(true); extra2d::tween::pos(screenWidth / 2.0f, screenHeight / 2.0f - 300.0f),
if (shareBtn_) opts)
shareBtn_->setEnabled(true); .call([this]() {
animationDone_ = true;
if (restartBtn_)
restartBtn_->setEnabled(true);
if (menuBtn_)
menuBtn_->setEnabled(true);
if (shareBtn_)
shareBtn_->setEnabled(true);
})
.start();
} }
void GameOverLayer::initPanel(int score, float screenHeight) { void GameOverLayer::initPanel(int score, float screenHeight) {

View File

@ -8,63 +8,78 @@
namespace flappybird { namespace flappybird {
Bird::Bird() { Bird::Bird() {
// 注意:不要在构造函数中调用 initAnimations()
// 因为此时 weak_from_this() 还不能使用
setStatus(Status::Idle); setStatus(Status::Idle);
} }
Bird::~Bird() = default;
void Bird::onEnter() { void Bird::onEnter() {
Node::onEnter(); Node::onEnter();
// 在 onEnter 中初始化动画,此时 weak_from_this() 可用 if (!sprite_) {
if (!animSprite_) {
initAnimations(); initAnimations();
} }
} }
Bird::~Bird() = default;
void Bird::initAnimations() { void Bird::initAnimations() {
// 随机选择小鸟颜色0-2 // 随机选择小鸟颜色0-2
int colorMode = extra2d::randomInt(0, 2); int colorMode = extra2d::randomInt(0, 2);
std::string prefix = "bird" + std::to_string(colorMode) + "_"; std::string prefix = "bird" + std::to_string(colorMode) + "_";
// 创建动画片段 // 加载动画帧序列: 0 -> 1 -> 2 -> 1
auto clip = extra2d::AnimationClip::create("bird_fly");
// 添加动画帧序列: 0 -> 1 -> 2 -> 1
// 注意:每个颜色只有 0, 1, 2 三个帧,没有 3
int frameSequence[] = {0, 1, 2, 1}; int frameSequence[] = {0, 1, 2, 1};
for (int frameIndex : frameSequence) { for (int frameIndex : frameSequence) {
auto frameSprite = ResLoader::getKeyFrame(prefix + std::to_string(frameIndex)); auto frameSprite = ResLoader::getKeyFrame(prefix + std::to_string(frameIndex));
if (frameSprite) { if (frameSprite) {
extra2d::AnimationFrame frame; frames_.push_back(frameSprite);
frame.spriteFrame = frameSprite;
frame.delay = 100.0f; // 100毫秒 = 0.1秒
clip->addFrame(std::move(frame));
} else { } else {
E2D_LOG_WARN("无法加载动画帧: {}{}", prefix, frameIndex); E2D_LOG_WARN("无法加载动画帧: {}{}", prefix, frameIndex);
} }
} }
// 创建动画精灵 // 创建精灵
if (clip->getFrameCount() > 0) { if (!frames_.empty()) {
clip->setLooping(true); sprite_ = extra2d::Sprite::create();
animSprite_ = extra2d::AnimatedSprite::create(clip); setCurrentFrame(0);
// 精灵图动画不应应用帧变换(避免覆盖节点位置) addChild(sprite_);
animSprite_->setApplyFrameTransform(false); E2D_LOG_INFO("小鸟动画创建成功: 颜色={}, 帧数={}", colorMode, frames_.size());
animSprite_->play();
addChild(animSprite_);
E2D_LOG_INFO("小鸟动画创建成功: 颜色={}, 帧数={}, running={}, animSprite父节点={}",
colorMode, clip->getFrameCount(), isRunning(),
animSprite_->getParent() ? "" : "");
} else { } else {
E2D_LOG_ERROR("小鸟动画创建失败: 没有找到任何动画帧"); E2D_LOG_ERROR("小鸟动画创建失败: 没有找到任何动画帧");
} }
} }
void Bird::setCurrentFrame(int frameIndex) {
if (frames_.empty() || !sprite_) return;
frameIndex = frameIndex % static_cast<int>(frames_.size());
currentFrame_ = frameIndex;
auto& frame = frames_[frameIndex];
sprite_->setTexture(frame->getTexture());
sprite_->setTextureRect(frame->getRect());
}
void Bird::updateFrameAnimation(float dt) {
if (frames_.empty() || status_ == Status::Still) return;
frameTimer_ += dt;
float interval = frameInterval_;
if (status_ == Status::StartToFly) {
interval = 0.05f; // 2倍速度
}
while (frameTimer_ >= interval) {
frameTimer_ -= interval;
setCurrentFrame((currentFrame_ + 1) % static_cast<int>(frames_.size()));
}
}
void Bird::onUpdate(float dt) { void Bird::onUpdate(float dt) {
extra2d::Node::onUpdate(dt); extra2d::Node::onUpdate(dt);
// 更新帧动画
updateFrameAnimation(dt);
// 处理闲置动画(上下浮动) // 处理闲置动画(上下浮动)
if (status_ == Status::Idle) { if (status_ == Status::Idle) {
idleTimer_ += dt; idleTimer_ += dt;
@ -73,15 +88,15 @@ void Bird::onUpdate(float dt) {
} }
void Bird::onRender(extra2d::RenderBackend& renderer) { void Bird::onRender(extra2d::RenderBackend& renderer) {
// 动画精灵会自动渲染,这里只需要处理旋转和偏移 // 精灵会自动渲染,这里只需要处理旋转和偏移
if (animSprite_) { if (sprite_) {
animSprite_->setRotation(rotation_); sprite_->setRotation(rotation_);
// 应用闲置偏移 // 应用闲置偏移
if (status_ == Status::Idle) { if (status_ == Status::Idle) {
animSprite_->setPosition(extra2d::Vec2(0.0f, idleOffset_)); sprite_->setPosition(extra2d::Vec2(0.0f, idleOffset_));
} else { } else {
animSprite_->setPosition(extra2d::Vec2(0.0f, 0.0f)); sprite_->setPosition(extra2d::Vec2(0.0f, 0.0f));
} }
} }
@ -142,26 +157,18 @@ void Bird::setStatus(Status status) {
switch (status) { switch (status) {
case Status::Still: case Status::Still:
// 停止所有动画 // 停止所有动画
if (animSprite_) { frameTimer_ = 0.0f;
animSprite_->pause();
}
break; break;
case Status::Idle: case Status::Idle:
// 开始闲置动画 // 开始闲置动画
if (animSprite_) { frameInterval_ = 0.1f; // 正常速度
animSprite_->setPlaybackSpeed(1.0f); // 正常速度
animSprite_->play();
}
idleTimer_ = 0.0f; idleTimer_ = 0.0f;
break; break;
case Status::StartToFly: case Status::StartToFly:
// 停止闲置动画,加速翅膀扇动 // 停止闲置动画,加速翅膀扇动
idleOffset_ = 0.0f; idleOffset_ = 0.0f;
if (animSprite_) {
animSprite_->setPlaybackSpeed(2.0f); // 2倍速度 = 0.05秒每帧
}
break; break;
case Status::Fly: case Status::Fly:

View File

@ -98,15 +98,29 @@ private:
*/ */
void initAnimations(); void initAnimations();
/**
* @brief
*/
void updateFrameAnimation(float dt);
/**
* @brief
*/
void setCurrentFrame(int frameIndex);
bool living_ = true; // 是否存活 bool living_ = true; // 是否存活
float speed_ = 0.0f; // 垂直速度 float speed_ = 0.0f; // 垂直速度
float rotation_ = 0.0f; // 旋转角度 float rotation_ = 0.0f; // 旋转角度
Status status_ = Status::Idle; // 当前状态 Status status_ = Status::Idle; // 当前状态
// 动画相关 // 动画相关
extra2d::Ptr<extra2d::AnimatedSprite> animSprite_; // 动画精灵 extra2d::Ptr<extra2d::Sprite> sprite_; // 精灵
float idleTimer_ = 0.0f; // 闲置动画计时器 std::vector<extra2d::Ptr<extra2d::SpriteFrame>> frames_; // 动画帧
float idleOffset_ = 0.0f; // 闲置偏移量 int currentFrame_ = 0; // 当前帧索引
float frameTimer_ = 0.0f; // 帧计时器
float frameInterval_ = 0.1f; // 帧间隔(秒)
float idleTimer_ = 0.0f; // 闲置动画计时器
float idleOffset_ = 0.0f; // 闲置偏移量
// 物理常量 // 物理常量
static constexpr float gravity = 1440.0f; // 重力加速度 static constexpr float gravity = 1440.0f; // 重力加速度

View File

@ -10,7 +10,7 @@ local example_dir = os.scriptdir()
target("flappy_bird") target("flappy_bird")
set_kind("binary") set_kind("binary")
add_files("*.cpp") add_files("*.cpp")
add_includedirs("../../Extra2D/include") add_includedirs("../../include")
add_deps("extra2d") add_deps("extra2d")
-- 使用与主项目相同的平台配置 -- 使用与主项目相同的平台配置

View File

@ -10,7 +10,7 @@ local example_dir = os.scriptdir()
target("hello_world") target("hello_world")
set_kind("binary") set_kind("binary")
add_files("main.cpp") add_files("main.cpp")
add_includedirs("../../Extra2D/include") add_includedirs("../../include")
add_deps("extra2d") add_deps("extra2d")
-- 使用与主项目相同的平台配置 -- 使用与主项目相同的平台配置

View File

@ -10,7 +10,7 @@ local example_dir = os.scriptdir()
target("push_box") target("push_box")
set_kind("binary") set_kind("binary")
add_files("*.cpp") add_files("*.cpp")
add_includedirs("../../Extra2D/include") add_includedirs("../../include")
add_deps("extra2d") add_deps("extra2d")
-- 使用与主项目相同的平台配置 -- 使用与主项目相同的平台配置

View File

@ -10,7 +10,7 @@ local example_dir = os.scriptdir()
target("spatial_index_demo") target("spatial_index_demo")
set_kind("binary") set_kind("binary")
add_files("main.cpp") add_files("main.cpp")
add_includedirs("../../Extra2D/include") add_includedirs("../../include")
add_deps("extra2d") add_deps("extra2d")
-- 使用与主项目相同的平台配置 -- 使用与主项目相同的平台配置

View File

@ -1,52 +0,0 @@
#pragma once
#include <core/math_types.h>
#include <core/types.h>
#include <string>
#include <vector>
namespace extra2d {
// ============================================================================
// ALS 图层信息
// ============================================================================
struct AlsLayerInfo {
std::string aniPath; // 子动画的 ANI 文件路径
int zOrder = 0; // 层级顺序
Vec2 offset; // 层偏移
};
// ============================================================================
// ALS 解析结果
// ============================================================================
struct AlsParseResult {
bool success = false;
std::string errorMessage;
std::vector<AlsLayerInfo> layers;
};
// ============================================================================
// AlsParser - ALS 复合动画文件解析器
// 解析 .als 文件获取多层动画的图层信息
// ============================================================================
class AlsParser {
public:
AlsParser() = default;
/// 从文件解析
AlsParseResult parse(const std::string &filePath);
/// 从内存内容解析
AlsParseResult parseFromMemory(const std::string &content,
const std::string &basePath = "");
/// 设置基础路径
void setBasePath(const std::string &basePath) { basePath_ = basePath; }
private:
std::string basePath_;
std::string resolvePath(const std::string &relativePath) const;
};
} // namespace extra2d

View File

@ -1,68 +0,0 @@
#pragma once
#include <cstdint>
#include <animation/ani_parser.h>
#include <animation/animation_cache.h>
#include <core/types.h>
#include <functional>
#include <string>
namespace extra2d {
// ============================================================================
// DNF ANI 二进制格式中的节点类型枚举
// ============================================================================
enum class AniNodeType : uint16_t {
Loop = 0,
Shadow = 1,
Coord = 3,
ImageRate = 7,
ImageRotate = 8,
RGBA = 9,
Interpolation = 10,
GraphicEffect = 11,
Delay = 12,
DamageType = 13,
DamageBox = 14,
AttackBox = 15,
PlaySound = 16,
Preload = 17,
Spectrum = 18,
SetFlag = 23,
FlipType = 24,
LoopStart = 25,
LoopEnd = 26,
Clip = 27,
Operation = 28,
};
// ============================================================================
// AniBinaryParser - ANI 二进制格式解析器
// 参考 DNF-Porting 的 PvfAnimation 实现
// ============================================================================
class AniBinaryParser {
public:
AniBinaryParser() = default;
/// 从二进制数据解析
AniParseResult parse(const uint8_t *data, size_t length);
/// 从文件解析
AniParseResult parseFromFile(const std::string &filePath);
/// 设置路径替换回调
void setPathResolver(PathResolveCallback callback) {
pathResolver_ = std::move(callback);
}
/// 设置基础路径
void setBasePath(const std::string &basePath) { basePath_ = basePath; }
private:
PathResolveCallback pathResolver_;
std::string basePath_;
std::string resolvePath(const std::string &relativePath) const;
};
} // namespace extra2d

View File

@ -1,50 +0,0 @@
#pragma once
#include <animation/animation_cache.h>
#include <animation/animation_clip.h>
#include <core/types.h>
#include <functional>
#include <string>
namespace extra2d {
// ============================================================================
// ANI 文件解析结果
// ============================================================================
struct AniParseResult {
bool success = false;
std::string errorMessage;
Ptr<AnimationClip> clip;
};
// ============================================================================
// AniParser - ANI 脚本文件解析器
// 将原始 ANI 文件格式解析为 AnimationClip 数据
// ============================================================================
class AniParser {
public:
AniParser() = default;
/// 从文件解析
AniParseResult parse(const std::string &filePath);
/// 从内存内容解析
AniParseResult parseFromMemory(const std::string &content,
const std::string &basePath = "");
/// 设置路径替换回调(对应原始 AdditionalOptions
void setPathResolver(PathResolveCallback callback) {
pathResolver_ = std::move(callback);
}
/// 设置基础路径(用于解析相对路径)
void setBasePath(const std::string &basePath) { basePath_ = basePath; }
private:
PathResolveCallback pathResolver_;
std::string basePath_;
std::string resolvePath(const std::string &relativePath) const;
};
} // namespace extra2d

View File

@ -1,128 +0,0 @@
#pragma once
#include <array>
#include <cstdint>
#include <animation/animation_cache.h>
#include <animation/animation_controller.h>
#include <scene/sprite.h>
#include <string>
#include <unordered_map>
#include <vector>
namespace extra2d {
// ============================================================================
// AnimatedSprite - 动画精灵节点
// 将 AnimationController 与 Sprite 渲染桥接,接入场景图
// ============================================================================
class AnimatedSprite : public Sprite {
public:
AnimatedSprite();
~AnimatedSprite() override = default;
// ------ 静态工厂 ------
static Ptr<AnimatedSprite> create();
static Ptr<AnimatedSprite> create(Ptr<AnimationClip> clip);
static Ptr<AnimatedSprite> create(const std::string &aniFilePath);
// ------ 动画绑定 ------
void setAnimationClip(Ptr<AnimationClip> clip);
void loadAnimation(const std::string &aniFilePath);
Ptr<AnimationClip> getAnimationClip() const;
// ------ 动画字典 ------
void addAnimation(const std::string &name, Ptr<AnimationClip> clip);
void play(const std::string &name, bool loop = true);
bool hasAnimation(const std::string &name) const;
Ptr<AnimationClip> getAnimation(const std::string &name) const;
const std::string &getCurrentAnimationName() const;
// ------ 播放控制(委托 controller_------
void play();
void pause();
void resume();
void stop();
void reset();
bool isPlaying() const;
bool isPaused() const;
bool isStopped() const;
// ------ 属性控制 ------
void setLooping(bool loop);
bool isLooping() const;
void setPlaybackSpeed(float speed);
float getPlaybackSpeed() const;
// ------ 帧控制 ------
void setFrameIndex(size_t index);
size_t getCurrentFrameIndex() const;
size_t getTotalFrames() const;
void nextFrame();
void prevFrame();
// ------ 帧范围限制 ------
/// 设置帧播放范围(用于精灵图动画,限制在指定范围内循环)
/// @param start 起始帧索引(包含)
/// @param end 结束帧索引(包含),-1表示不限制
void setFrameRange(int start, int end = -1);
/// 获取当前帧范围
/// @return pair<起始帧, 结束帧>,结束帧为-1表示不限制
std::pair<int, int> getFrameRange() const;
/// 清除帧范围限制(恢复播放所有帧)
void clearFrameRange();
/// 检查是否设置了帧范围限制
bool hasFrameRange() const;
// ------ 回调 ------
void setCompletionCallback(AnimationController::CompletionCallback cb);
void setKeyframeCallback(AnimationController::KeyframeCallback cb);
void setSoundTriggerCallback(AnimationController::SoundTriggerCallback cb);
// ------ 碰撞盒访问(当前帧)------
const std::vector<std::array<int32_t, 6>> &getCurrentDamageBoxes() const;
const std::vector<std::array<int32_t, 6>> &getCurrentAttackBoxes() const;
// ------ 帧变换控制 ------
/// 设置是否由动画帧数据覆盖节点的 position/scale/rotation
/// ANI 动画需要开启(默认),精灵图动画应关闭
void setApplyFrameTransform(bool apply) { applyFrameTransform_ = apply; }
bool isApplyFrameTransform() const { return applyFrameTransform_; }
// ------ 自动播放 ------
void setAutoPlay(bool autoPlay) { autoPlay_ = autoPlay; }
bool isAutoPlay() const { return autoPlay_; }
// ------ 直接控制器访问 ------
AnimationController &getController() { return controller_; }
const AnimationController &getController() const { return controller_; }
protected:
void onUpdate(float dt) override;
void onEnter() override;
private:
AnimationController controller_;
bool autoPlay_ = false;
bool applyFrameTransform_ = true;
// 动画字典
std::unordered_map<std::string, Ptr<AnimationClip>> animations_;
std::string currentAnimationName_;
static const std::string emptyString_;
// 帧范围限制(用于精灵图动画)
int frameRangeStart_ = 0; // 起始帧索引
int frameRangeEnd_ = -1; // 结束帧索引,-1表示不限制
// 空碰撞盒列表(用于无帧时返回引用)
static const std::vector<std::array<int32_t, 6>> emptyBoxes_;
void applyFrame(const AnimationFrame &frame);
void onFrameChanged(size_t oldIdx, size_t newIdx,
const AnimationFrame &frame);
};
} // namespace extra2d

View File

@ -1,102 +0,0 @@
#pragma once
#include <animation/animation_clip.h>
#include <functional>
#include <mutex>
#include <string>
#include <unordered_map>
namespace extra2d {
// 路径替换回调(对应原始 AdditionalOptions
using PathResolveCallback = std::function<std::string(const std::string &)>;
// ============================================================================
// AnimationCache - 动画片段全局缓存(借鉴 Cocos AnimationCache
// 同一 ANI 文件只解析一次,后续直接复用数据
// ============================================================================
class AnimationCache {
public:
static AnimationCache &getInstance() {
static AnimationCache instance;
return instance;
}
// ------ 加载与获取 ------
/// 从文件加载(自动缓存),已缓存则直接返回
/// 注意:实际的 ANI 解析逻辑在 AniParser 中实现
/// 此方法在 animation_cache.cpp 中实现,依赖 AniParser
Ptr<AnimationClip> loadClip(const std::string &aniFilePath);
/// 从缓存获取(不触发加载)
Ptr<AnimationClip> getClip(const std::string &name) const {
std::lock_guard<std::mutex> lock(mutex_);
auto it = clips_.find(name);
if (it != clips_.end())
return it->second;
return nullptr;
}
/// 手动添加到缓存
void addClip(Ptr<AnimationClip> clip, const std::string &name) {
std::lock_guard<std::mutex> lock(mutex_);
clips_[name] = std::move(clip);
}
// ------ 缓存管理 ------
bool has(const std::string &name) const {
std::lock_guard<std::mutex> lock(mutex_);
return clips_.find(name) != clips_.end();
}
void removeClip(const std::string &name) {
std::lock_guard<std::mutex> lock(mutex_);
clips_.erase(name);
}
/// 移除未被外部引用的动画片段
void removeUnusedClips() {
std::lock_guard<std::mutex> lock(mutex_);
for (auto it = clips_.begin(); it != clips_.end();) {
if (it->second.use_count() == 1) {
it = clips_.erase(it);
} else {
++it;
}
}
}
void clear() {
std::lock_guard<std::mutex> lock(mutex_);
clips_.clear();
}
size_t count() const {
std::lock_guard<std::mutex> lock(mutex_);
return clips_.size();
}
// ------ 路径配置 ------
void setPathResolver(PathResolveCallback resolver) {
pathResolver_ = std::move(resolver);
}
PathResolveCallback getPathResolver() const { return pathResolver_; }
private:
AnimationCache() = default;
~AnimationCache() = default;
AnimationCache(const AnimationCache &) = delete;
AnimationCache &operator=(const AnimationCache &) = delete;
mutable std::mutex mutex_;
std::unordered_map<std::string, Ptr<AnimationClip>> clips_;
PathResolveCallback pathResolver_;
};
// 便捷宏
#define E2D_ANIMATION_CACHE() ::extra2d::AnimationCache::getInstance()
} // namespace extra2d

View File

@ -1,184 +0,0 @@
#pragma once
#include <cassert>
#include <animation/animation_frame.h>
#include <animation/sprite_frame_cache.h>
#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

View File

@ -1,108 +0,0 @@
#pragma once
#include <animation/animation_clip.h>
#include <animation/interpolation_engine.h>
#include <core/types.h>
#include <functional>
#include <string>
namespace extra2d {
// ============================================================================
// 动画播放状态
// ============================================================================
enum class AnimPlayState : uint8 { Stopped, Playing, Paused };
// ============================================================================
// AnimationController - 动画播放控制器
// 借鉴 Cocos Creator 的 AnimationState纯播放逻辑不持有渲染资源
// ============================================================================
class AnimationController {
public:
// 回调类型定义
using FrameChangeCallback = std::function<void(size_t oldIdx, size_t newIdx,
const AnimationFrame &frame)>;
using KeyframeCallback = std::function<void(int flagIndex)>;
using SoundTriggerCallback = std::function<void(const std::string &path)>;
using CompletionCallback = std::function<void()>;
AnimationController() = default;
// ------ 绑定动画数据 ------
void setClip(Ptr<AnimationClip> clip);
Ptr<AnimationClip> getClip() const { return clip_; }
// ------ 播放控制 ------
void play();
void pause();
void resume();
void stop();
void reset();
// ------ 帧控制 ------
void setFrameIndex(size_t index);
void nextFrame();
void prevFrame();
// ------ 核心更新(每帧调用)------
void update(float dt);
// ------ 状态查询 ------
AnimPlayState getState() const { return state_; }
bool isPlaying() const { return state_ == AnimPlayState::Playing; }
bool isPaused() const { return state_ == AnimPlayState::Paused; }
bool isStopped() const { return state_ == AnimPlayState::Stopped; }
size_t getCurrentFrameIndex() const { return currentFrameIndex_; }
size_t getTotalFrames() const;
const AnimationFrame &getCurrentFrame() const;
float getPlaybackSpeed() const { return playbackSpeed_; }
void setPlaybackSpeed(float speed) { playbackSpeed_ = speed; }
bool isLooping() const;
void setLooping(bool loop);
// ------ 插值状态 ------
float getInterpolationFactor() const { return interpolationFactor_; }
bool isInterpolating() const { return interpolating_; }
// ------ 回调注册 ------
void setFrameChangeCallback(FrameChangeCallback cb) {
onFrameChange_ = std::move(cb);
}
void setKeyframeCallback(KeyframeCallback cb) { onKeyframe_ = std::move(cb); }
void setSoundTriggerCallback(SoundTriggerCallback cb) {
onSoundTrigger_ = std::move(cb);
}
void setCompletionCallback(CompletionCallback cb) {
onComplete_ = std::move(cb);
}
private:
Ptr<AnimationClip> clip_;
AnimPlayState state_ = AnimPlayState::Stopped;
size_t currentFrameIndex_ = 0;
float accumulatedTime_ = 0.0f; // 当前帧已累积时间 (ms)
float playbackSpeed_ = 1.0f;
bool loopOverride_ = false; // 外部循环覆盖值
bool hasLoopOverride_ = false; // 是否使用外部循环覆盖
// 插值状态
bool interpolating_ = false;
float interpolationFactor_ = 0.0f;
// 回调
FrameChangeCallback onFrameChange_;
KeyframeCallback onKeyframe_;
SoundTriggerCallback onSoundTrigger_;
CompletionCallback onComplete_;
// 内部方法
void advanceFrame(size_t newIndex);
void processFrameProperties(const AnimationFrame &frame);
void updateInterpolation();
};
} // namespace extra2d

View File

@ -1,43 +0,0 @@
#pragma once
#include <cstdint>
#include <functional>
#include <string>
namespace extra2d {
// 前向声明
class Node;
// ============================================================================
// 动画事件类型
// ============================================================================
enum class AnimationEventType : uint32_t {
FrameChanged = 0x2001, // 帧切换
KeyframeHit = 0x2002, // 关键帧触发
SoundTrigger = 0x2003, // 音效触发
AnimationStart = 0x2004, // 动画开始播放
AnimationEnd = 0x2005, // 动画播放结束
AnimationLoop = 0x2006, // 动画循环一轮
};
// ============================================================================
// 动画事件数据
// ============================================================================
struct AnimationEvent {
AnimationEventType type;
size_t frameIndex = 0;
size_t previousFrameIndex = 0;
int keyframeFlag = -1;
std::string soundPath;
Node *source = nullptr;
};
// ============================================================================
// 动画事件回调类型
// ============================================================================
using AnimationEventCallback = std::function<void(const AnimationEvent &)>;
using KeyframeHitCallback = std::function<void(int flagIndex)>;
using AnimationCompleteCallback = std::function<void()>;
} // namespace extra2d

View File

@ -1,62 +0,0 @@
#pragma once
#include <array>
#include <cstdint>
#include <animation/frame_property.h>
#include <animation/sprite_frame.h>
#include <string>
#include <vector>
namespace extra2d {
// ============================================================================
// AnimationFrame - 单帧数据
// 引用 SpriteFrame 而非直接持有纹理(借鉴 Cocos 模式)
// 通过 FramePropertySet 支持不固定数据ANI Flag 系统增强版)
// ============================================================================
struct AnimationFrame {
// ------ 核心数据(固定部分)------
Ptr<SpriteFrame> spriteFrame; // 精灵帧引用Cocos 模式)
std::string texturePath; // 原始图片路径(用于解析时定位资源)
int textureIndex = 0; // 精灵图集索引
Vec2 offset; // 位置偏移
float delay = 100.0f; // 帧延迟(毫秒)
// ------ 碰撞盒数据DNF ANI 格式)------
std::vector<std::array<int32_t, 6>> damageBoxes; // 伤害碰撞盒
std::vector<std::array<int32_t, 6>> attackBoxes; // 攻击碰撞盒
// ------ 不固定数据(属性集合)------
FramePropertySet properties; // 类型安全的 Flag 系统
// ------ 便捷方法 ------
bool hasTexture() const {
return spriteFrame != nullptr && spriteFrame->isValid();
}
bool hasInterpolation() const {
return properties.getOr<bool>(FramePropertyKey::Interpolation, false);
}
bool hasKeyframeCallback() const {
return properties.has(FramePropertyKey::SetFlag);
}
int getKeyframeIndex() const {
return properties.getOr<int>(FramePropertyKey::SetFlag, -1);
}
Vec2 getEffectiveScale() const {
return properties.getOr<Vec2>(FramePropertyKey::ImageRate, Vec2::One());
}
float getEffectiveRotation() const {
return properties.getOr<float>(FramePropertyKey::ImageRotate, 0.0f);
}
Color getEffectiveColor() const {
return properties.getOr<Color>(FramePropertyKey::ColorTint, Colors::White);
}
};
} // namespace extra2d

View File

@ -1,108 +0,0 @@
#pragma once
#include <animation/animation_cache.h>
#include <animation/animation_clip.h>
#include <animation/animation_controller.h>
#include <animation/animation_event.h>
#include <animation/frame_renderer.h>
#include <scene/node.h>
#include <vector>
namespace extra2d {
// ============================================================================
// AnimationNode - 动画节点(继承 Node
// 使用 FrameRenderer 单渲染器策略,不依赖 Sprite 基类
// 适用于需要独立渲染控制的动画(如特效、复合动画图层)
// ============================================================================
class AnimationNode : public Node {
public:
AnimationNode();
~AnimationNode() override = default;
// ------ 静态工厂Cocos 风格)------
static Ptr<AnimationNode> create();
static Ptr<AnimationNode> create(Ptr<AnimationClip> clip);
static Ptr<AnimationNode> create(const std::string &aniFilePath);
// ------ 动画数据 ------
void setClip(Ptr<AnimationClip> clip);
Ptr<AnimationClip> getClip() const;
bool loadFromFile(const std::string &aniFilePath);
// ------ 播放控制 ------
void play();
void pause();
void resume();
void stop();
void reset();
bool isPlaying() const;
bool isPaused() const;
bool isStopped() const;
void setPlaybackSpeed(float speed);
float getPlaybackSpeed() const;
void setLooping(bool loop);
bool isLooping() const;
// ------ 帧控制 ------
void setFrameIndex(size_t index);
size_t getCurrentFrameIndex() const;
size_t getTotalFrames() const;
// ------ 事件回调 ------
void setKeyframeCallback(KeyframeHitCallback callback);
void setCompletionCallback(AnimationCompleteCallback callback);
void
setFrameChangeCallback(AnimationController::FrameChangeCallback callback);
void addEventListener(AnimationEventCallback callback);
// ------ 视觉属性 ------
void setTintColor(const Color &color);
Color getTintColor() const { return tintColor_; }
void setFlipX(bool flip) { flipX_ = flip; }
void setFlipY(bool flip) { flipY_ = flip; }
bool isFlipX() const { return flipX_; }
bool isFlipY() const { return flipY_; }
// ------ 自动播放 ------
void setAutoPlay(bool autoPlay) { autoPlay_ = autoPlay; }
bool isAutoPlay() const { return autoPlay_; }
// ------ 碰撞盒访问 ------
const std::vector<std::array<int32_t, 6>> &getCurrentDamageBoxes() const;
const std::vector<std::array<int32_t, 6>> &getCurrentAttackBoxes() const;
// ------ 查询 ------
Size getMaxFrameSize() const;
Rect getBoundingBox() const override;
// ------ 直接访问 ------
AnimationController &getController() { return controller_; }
const AnimationController &getController() const { return controller_; }
FrameRenderer &getFrameRenderer() { return frameRenderer_; }
const FrameRenderer &getFrameRenderer() const { return frameRenderer_; }
protected:
void onUpdate(float dt) override;
void onDraw(RenderBackend &renderer) override;
void onEnter() override;
void onExit() override;
private:
AnimationController controller_;
FrameRenderer frameRenderer_;
Color tintColor_ = Colors::White;
bool flipX_ = false;
bool flipY_ = false;
bool autoPlay_ = false;
std::vector<AnimationEventCallback> eventListeners_;
static const std::vector<std::array<int32_t, 6>> emptyBoxes_;
void setupControllerCallbacks();
void dispatchEvent(const AnimationEvent &event);
};
} // namespace extra2d

View File

@ -1,65 +0,0 @@
#pragma once
#include <animation/als_parser.h>
#include <animation/animation_event.h>
#include <animation/animation_node.h>
#include <scene/node.h>
#include <vector>
namespace extra2d {
// ============================================================================
// CompositeAnimation - ALS 多层复合动画节点
// 管理多个 AnimationNode 图层,统一控制播放
// 对应 DNF 的 ALS 格式(多层动画叠加)
// ============================================================================
class CompositeAnimation : public Node {
public:
CompositeAnimation() = default;
~CompositeAnimation() override = default;
// ------ 静态工厂 ------
static Ptr<CompositeAnimation> create();
static Ptr<CompositeAnimation> create(const std::string &alsFilePath);
// ------ 加载 ------
bool loadFromFile(const std::string &alsFilePath);
// ------ 图层管理 ------
void addLayer(Ptr<AnimationNode> node, int zOrder = 0);
void removeLayer(size_t index);
Ptr<AnimationNode> getLayer(size_t index) const;
Ptr<AnimationNode> getMainLayer() const;
size_t getLayerCount() const;
// ------ 统一播放控制 ------
void play();
void pause();
void resume();
void stop();
void reset();
void setPlaybackSpeed(float speed);
void setLooping(bool loop);
bool isPlaying() const;
bool isStopped() const;
// ------ 事件回调(绑定到主图层)------
void setKeyframeCallback(KeyframeHitCallback callback);
void setCompletionCallback(AnimationCompleteCallback callback);
void addEventListener(AnimationEventCallback callback);
// ------ 视觉属性(应用到所有图层)------
void setTintColor(const Color &color);
void setFlipX(bool flip);
void setFlipY(bool flip);
private:
struct LayerEntry {
Ptr<AnimationNode> node;
int zOrder = 0;
};
std::vector<LayerEntry> layers_;
};
} // namespace extra2d

View File

@ -1,210 +0,0 @@
#pragma once
#include <any>
#include <core/color.h>
#include <core/math_types.h>
#include <core/types.h>
#include <optional>
#include <string>
#include <unordered_map>
#include <variant>
#include <vector>
namespace extra2d {
// ============================================================================
// 帧属性键 - 强类型枚举替代原始 ANI 的字符串键
// ============================================================================
enum class FramePropertyKey : uint32 {
// 事件触发
SetFlag = 0x0001, // int: 关键帧回调索引
PlaySound = 0x0002, // string: 音效路径
// 变换属性
ImageRate = 0x0010, // Vec2: 缩放比例
ImageRotate = 0x0011, // float: 旋转角度(度)
ImageOffset = 0x0012, // Vec2: 额外位置偏移
// 视觉效果
BlendLinearDodge = 0x0020, // bool: 线性减淡
BlendAdditive = 0x0021, // bool: 加法混合
ColorTint = 0x0022, // Color: RGBA 颜色
// 控制标记
Interpolation = 0x0030, // bool: 启用到下一帧的插值
Loop = 0x0031, // bool: 全局循环标记
// DNF ANI 扩展属性
DamageType = 0x0040, // int: 伤害类型 (0=Normal, 1=SuperArmor, 2=Unbreakable)
Shadow = 0x0041, // bool: 阴影
FlipType = 0x0042, // int: 翻转类型 (1=Horizon, 2=Vertical, 3=All)
Coord = 0x0043, // int: 坐标系
LoopStart = 0x0044, // bool: 循环起始标记
LoopEnd = 0x0045, // int: 循环结束帧数
GraphicEffect = 0x0046, // int: 图形特效类型
ClipRegion = 0x0047, // vector<int>: 裁剪区域 [4个int16]
// 用户自定义扩展区间 (0x1000+)
UserDefined = 0x1000,
};
// ============================================================================
// 帧属性值 - variant 多态值(优化版本)
// 使用紧凑存储,常用小类型直接内联,大类型使用索引引用
// ============================================================================
// 前向声明
struct FramePropertyValue;
// 属性存储类型枚举
enum class PropertyValueType : uint8_t {
Empty = 0,
Bool = 1,
Int = 2,
Float = 3,
Vec2 = 4,
Color = 5,
String = 6, // 字符串使用索引引用
IntVector = 7, // vector<int> 使用索引引用
};
// 紧凑的属性值结构16字节
struct FramePropertyValue {
PropertyValueType type = PropertyValueType::Empty;
uint8_t padding[3] = {0};
// 使用结构体包装非平凡类型使其可以在union中使用
struct Vec2Storage {
float x, y;
Vec2Storage() = default;
Vec2Storage(const Vec2& v) : x(v.x), y(v.y) {}
operator Vec2() const { return Vec2(x, y); }
};
struct ColorStorage {
float r, g, b, a;
ColorStorage() = default;
ColorStorage(const Color& c) : r(c.r), g(c.g), b(c.b), a(c.a) {}
operator Color() const { return Color(r, g, b, a); }
};
union Data {
bool boolValue;
int intValue;
float floatValue;
Vec2Storage vec2Value;
ColorStorage colorValue;
uint32_t stringIndex; // 字符串池索引
uint32_t vectorIndex; // vector池索引
Data() : intValue(0) {} // 默认构造函数
~Data() {} // 析构函数
} data;
FramePropertyValue() : type(PropertyValueType::Empty) {}
explicit FramePropertyValue(bool v) : type(PropertyValueType::Bool) { data.boolValue = v; }
explicit FramePropertyValue(int v) : type(PropertyValueType::Int) { data.intValue = v; }
explicit FramePropertyValue(float v) : type(PropertyValueType::Float) { data.floatValue = v; }
explicit FramePropertyValue(const Vec2& v) : type(PropertyValueType::Vec2) { data.vec2Value = v; }
explicit FramePropertyValue(const Color& v) : type(PropertyValueType::Color) { data.colorValue = v; }
bool isInline() const {
return type <= PropertyValueType::Color;
}
bool isString() const { return type == PropertyValueType::String; }
bool isIntVector() const { return type == PropertyValueType::IntVector; }
};
// ============================================================================
// FramePropertyKey 的 hash 支持
// ============================================================================
struct FramePropertyKeyHash {
size_t operator()(FramePropertyKey key) const noexcept {
return std::hash<uint32>{}(static_cast<uint32>(key));
}
};
// ============================================================================
// FramePropertySet - 单帧属性集合(优化版本)
// 使用紧凑存储和线性探测哈希表,提高缓存命中率
// ============================================================================
class FramePropertySet {
public:
FramePropertySet() = default;
// ------ 设置属性 ------
void set(FramePropertyKey key, FramePropertyValue value);
void set(FramePropertyKey key, bool value) { set(key, FramePropertyValue(value)); }
void set(FramePropertyKey key, int value) { set(key, FramePropertyValue(value)); }
void set(FramePropertyKey key, float value) { set(key, FramePropertyValue(value)); }
void set(FramePropertyKey key, const Vec2& value) { set(key, FramePropertyValue(value)); }
void set(FramePropertyKey key, const Color& value) { set(key, FramePropertyValue(value)); }
void set(FramePropertyKey key, const std::string& value);
void set(FramePropertyKey key, const std::vector<int>& value);
void setCustom(const std::string &key, std::any value);
// ------ 类型安全获取 ------
template <typename T> std::optional<T> get(FramePropertyKey key) const;
template <typename T>
T getOr(FramePropertyKey key, const T &defaultValue) const {
auto result = get<T>(key);
return result.value_or(defaultValue);
}
std::optional<std::any> getCustom(const std::string &key) const;
// ------ 查询 ------
bool has(FramePropertyKey key) const;
bool hasCustom(const std::string &key) const;
bool empty() const { return properties_.empty() && customProperties_.empty(); }
size_t count() const { return properties_.size() + customProperties_.size(); }
// ------ 移除 ------
void remove(FramePropertyKey key);
void removeCustom(const std::string &key);
void clear();
// ------ 迭代 ------
using PropertyMap = std::unordered_map<FramePropertyKey, FramePropertyValue,
FramePropertyKeyHash>;
const PropertyMap &properties() const { return properties_; }
// ------ 链式 API ------
FramePropertySet &withSetFlag(int index);
FramePropertySet &withPlaySound(const std::string &path);
FramePropertySet &withImageRate(const Vec2 &scale);
FramePropertySet &withImageRotate(float degrees);
FramePropertySet &withColorTint(const Color &color);
FramePropertySet &withInterpolation(bool enabled = true);
FramePropertySet &withBlendLinearDodge(bool enabled = true);
FramePropertySet &withLoop(bool enabled = true);
private:
PropertyMap properties_;
std::unordered_map<std::string, std::any> customProperties_;
// 字符串池和vector池用于存储大对象
mutable std::vector<std::string> stringPool_;
mutable std::vector<std::vector<int>> vectorPool_;
mutable uint32_t nextStringIndex_ = 0;
mutable uint32_t nextVectorIndex_ = 0;
uint32_t allocateString(const std::string& str);
uint32_t allocateVector(const std::vector<int>& vec);
const std::string* getString(uint32_t index) const;
const std::vector<int>* getVector(uint32_t index) const;
};
// 模板特化声明
template <> std::optional<bool> FramePropertySet::get<bool>(FramePropertyKey key) const;
template <> std::optional<int> FramePropertySet::get<int>(FramePropertyKey key) const;
template <> std::optional<float> FramePropertySet::get<float>(FramePropertyKey key) const;
template <> std::optional<Vec2> FramePropertySet::get<Vec2>(FramePropertyKey key) const;
template <> std::optional<Color> FramePropertySet::get<Color>(FramePropertyKey key) const;
template <> std::optional<std::string> FramePropertySet::get<std::string>(FramePropertyKey key) const;
template <> std::optional<std::vector<int>> FramePropertySet::get<std::vector<int>>(FramePropertyKey key) const;
} // namespace extra2d

View File

@ -1,57 +0,0 @@
#pragma once
#include <animation/animation_frame.h>
#include <animation/interpolation_engine.h>
#include <animation/sprite_frame.h>
#include <animation/sprite_frame_cache.h>
#include <graphics/render_backend.h>
#include <vector>
namespace extra2d {
// ============================================================================
// FrameRenderer - 帧渲染器
// 单渲染器 + SpriteFrame 引用策略,替代 N帧=N个Sprite 的旧设计
// 负责预加载帧的 SpriteFrame、渲染当前帧、处理混合模式
// ============================================================================
class FrameRenderer {
public:
FrameRenderer() = default;
// ------ 预加载 ------
// 解析所有帧的 SpriteFrame通过 SpriteFrameCache
bool preloadFrames(const std::vector<AnimationFrame> &frames);
void releaseFrames();
// ------ 渲染当前帧 ------
void renderFrame(RenderBackend &renderer, const AnimationFrame &frame,
size_t frameIndex, const Vec2 &position, float nodeOpacity,
const Color &tintColor, bool flipX, bool flipY);
// ------ 渲染插值帧 ------
void renderInterpolated(RenderBackend &renderer,
const AnimationFrame &fromFrame, size_t fromIndex,
const InterpolatedProperties &props,
const Vec2 &position, float nodeOpacity,
const Color &tintColor, bool flipX, bool flipY);
// ------ 混合模式映射 ------
static BlendMode mapBlendMode(const FramePropertySet &props);
// ------ 查询 ------
Ptr<SpriteFrame> getSpriteFrame(size_t frameIndex) const;
Size getMaxFrameSize() const { return maxFrameSize_; }
bool isLoaded() const { return !spriteFrames_.empty(); }
private:
std::vector<Ptr<SpriteFrame>> spriteFrames_;
Size maxFrameSize_;
void drawSpriteFrame(RenderBackend &renderer, Ptr<SpriteFrame> sf,
const Vec2 &position, const Vec2 &offset,
const Vec2 &scale, float rotation, float opacity,
const Color &tint, bool flipX, bool flipY,
BlendMode blend);
};
} // namespace extra2d

View File

@ -1,106 +0,0 @@
#pragma once
#include <cmath>
#include <animation/animation_frame.h>
#include <core/color.h>
#include <core/math_types.h>
#include <core/types.h>
namespace extra2d {
// ============================================================================
// 插值结果 - 两帧之间的插值后属性
// ============================================================================
struct InterpolatedProperties {
Vec2 position;
Vec2 scale = Vec2::One();
float rotation = 0.0f;
Color color = Colors::White;
};
// ============================================================================
// 插值曲线类型
// ============================================================================
enum class InterpolationCurve : uint8 {
Linear, // 线性(原始系统的 uniform velocity
EaseIn, // 缓入
EaseOut, // 缓出
EaseInOut, // 缓入缓出
};
// ============================================================================
// InterpolationEngine - 帧间属性插值计算(静态方法,无状态)
// 独立于 AnimationController可复用于其他系统
// ============================================================================
class InterpolationEngine {
public:
/// 核心插值计算:根据 t 因子 (0~1) 计算两帧之间的插值属性
static InterpolatedProperties
interpolate(const AnimationFrame &from, const AnimationFrame &to, float t,
InterpolationCurve curve = InterpolationCurve::Linear) {
float curvedT = applyCurve(t, curve);
InterpolatedProperties result;
result.position = lerpPosition(from, to, curvedT);
result.scale = lerpScale(from, to, curvedT);
result.rotation = lerpRotation(from, to, curvedT);
result.color = lerpColor(from, to, curvedT);
return result;
}
/// 位置插值
static Vec2 lerpPosition(const AnimationFrame &from, const AnimationFrame &to,
float t) {
return Vec2::lerp(from.offset, to.offset, t);
}
/// 缩放插值
static Vec2 lerpScale(const AnimationFrame &from, const AnimationFrame &to,
float t) {
Vec2 fromScale = from.getEffectiveScale();
Vec2 toScale = to.getEffectiveScale();
return Vec2::lerp(fromScale, toScale, t);
}
/// 旋转插值
static float lerpRotation(const AnimationFrame &from,
const AnimationFrame &to, float t) {
float fromRot = from.getEffectiveRotation();
float toRot = to.getEffectiveRotation();
return math::lerp(fromRot, toRot, t);
}
/// 颜色插值
static Color lerpColor(const AnimationFrame &from, const AnimationFrame &to,
float t) {
Color fromColor = from.getEffectiveColor();
Color toColor = to.getEffectiveColor();
return Color::lerp(fromColor, toColor, t);
}
/// 应用曲线函数
static float applyCurve(float t, InterpolationCurve curve) {
t = math::clamp(t, 0.0f, 1.0f);
switch (curve) {
case InterpolationCurve::Linear:
return t;
case InterpolationCurve::EaseIn:
return t * t;
case InterpolationCurve::EaseOut:
return t * (2.0f - t);
case InterpolationCurve::EaseInOut:
if (t < 0.5f)
return 2.0f * t * t;
else
return -1.0f + (4.0f - 2.0f * t) * t;
}
return t;
}
};
} // namespace extra2d

168
include/animation/tween.h Normal file
View File

@ -0,0 +1,168 @@
#pragma once
#include <animation/tween_easing.h>
#include <core/color.h>
#include <core/math_types.h>
#include <functional>
#include <initializer_list>
#include <memory>
#include <optional>
#include <string>
#include <vector>
namespace extra2d {
class Node;
struct TweenOptions {
TweenEasing easing = TweenEasing::Linear;
std::function<void()> onStart = nullptr;
std::function<void(float progress)> onUpdate = nullptr;
std::function<void()> onComplete = nullptr;
};
struct TweenProperty {
std::optional<Vec2> position;
std::optional<Vec2> scale;
std::optional<float> rotation;
std::optional<float> opacity;
std::optional<Color> color;
};
class TweenAction {
public:
enum class Type : uint8_t {
Interval,
Delay,
Call,
Parallel,
};
virtual ~TweenAction() = default;
virtual void update(float dt) = 0;
virtual bool isFinished() const = 0;
virtual float getDuration() const = 0;
virtual float getElapsed() const = 0;
virtual void reset() = 0;
Type getType() const { return type_; }
protected:
TweenAction(Type type) : type_(type) {}
Type type_;
};
class Tween {
public:
using Ptr = std::shared_ptr<Tween>;
using Callback = std::function<void()>;
explicit Tween(Node *target);
Tween(Node *target, const std::string &name);
~Tween();
Tween(const Tween &) = delete;
Tween &operator=(const Tween &) = delete;
Tween(Tween &&) = default;
Tween &operator=(Tween &&) = default;
static Tween create(Node *target);
static Tween create(Node *target, const std::string &name);
Tween &to(float duration, const TweenProperty &props,
const TweenOptions &options = {});
Tween &by(float duration, const TweenProperty &props,
const TweenOptions &options = {});
Tween &set(const TweenProperty &props);
Tween &delay(float seconds);
Tween &call(Callback callback);
Tween &parallel();
Tween &endParallel();
Tween &union_();
Tween &repeat(int times);
Tween &repeatForever();
Tween &yoyo(bool enabled = true);
void start();
void stop();
void pause();
void resume();
void update(float dt);
bool isFinished() const;
bool isPlaying() const;
bool isPaused() const;
float getTotalDuration() const;
void setName(const std::string &name) { name_ = name; }
const std::string &getName() const { return name_; }
private:
Node *target_ = nullptr;
std::string name_;
std::vector<std::unique_ptr<TweenAction>> actions_;
size_t currentActionIndex_ = 0;
bool playing_ = false;
bool paused_ = false;
bool finished_ = false;
int repeatCount_ = 1;
int currentRepeat_ = 0;
bool yoyo_ = false;
bool reverse_ = false;
bool repeatForever_ = false;
bool inParallel_ = false;
std::vector<std::unique_ptr<TweenAction>> parallelActions_;
void addAction(std::unique_ptr<TweenAction> action);
void advanceToNextAction();
void resetAllActions();
};
namespace tween {
inline TweenProperty pos(const Vec2 &p) {
TweenProperty props;
props.position = p;
return props;
}
inline TweenProperty pos(float x, float y) { return pos(Vec2(x, y)); }
inline TweenProperty scl(const Vec2 &s) {
TweenProperty props;
props.scale = s;
return props;
}
inline TweenProperty scl(float s) { return scl(Vec2(s, s)); }
inline TweenProperty rot(float degrees) {
TweenProperty props;
props.rotation = degrees;
return props;
}
inline TweenProperty opa(float opacity) {
TweenProperty props;
props.opacity = opacity;
return props;
}
inline TweenProperty col(const Color &c) {
TweenProperty props;
props.color = c;
return props;
}
TweenProperty combine(std::initializer_list<TweenProperty> props);
} // namespace tween
} // namespace extra2d

View File

@ -0,0 +1,91 @@
#pragma once
#include <cstdint>
namespace extra2d {
enum class TweenEasing : uint8_t {
Linear,
QuadIn,
QuadOut,
QuadInOut,
CubicIn,
CubicOut,
CubicInOut,
QuartIn,
QuartOut,
QuartInOut,
QuintIn,
QuintOut,
QuintInOut,
SineIn,
SineOut,
SineInOut,
ExpoIn,
ExpoOut,
ExpoInOut,
CircIn,
CircOut,
CircInOut,
ElasticIn,
ElasticOut,
ElasticInOut,
BackIn,
BackOut,
BackInOut,
BounceIn,
BounceOut,
BounceInOut,
Smooth,
Fade,
};
class EasingFunctions {
public:
using EasingFunc = float (*)(float);
static float apply(float t, TweenEasing easing);
static EasingFunc get(TweenEasing easing);
static float linear(float t);
static float quadIn(float t);
static float quadOut(float t);
static float quadInOut(float t);
static float cubicIn(float t);
static float cubicOut(float t);
static float cubicInOut(float t);
static float quartIn(float t);
static float quartOut(float t);
static float quartInOut(float t);
static float quintIn(float t);
static float quintOut(float t);
static float quintInOut(float t);
static float sineIn(float t);
static float sineOut(float t);
static float sineInOut(float t);
static float expoIn(float t);
static float expoOut(float t);
static float expoInOut(float t);
static float circIn(float t);
static float circOut(float t);
static float circInOut(float t);
static float elasticIn(float t);
static float elasticOut(float t);
static float elasticInOut(float t);
static float backIn(float t);
static float backOut(float t);
static float backInOut(float t);
static float bounceIn(float t);
static float bounceOut(float t);
static float bounceInOut(float t);
static float smooth(float t);
static float fade(float t);
private:
static constexpr float PI = 3.14159265358979323846f;
static constexpr float HALF_PI = 1.57079632679489661923f;
static constexpr float BACK_CONST = 1.70158f;
static constexpr float ELASTIC_CONST = 2.0f * PI / 3.0f;
};
} // namespace extra2d

View File

@ -39,20 +39,8 @@
// Animation // Animation
#include <animation/sprite_frame.h> #include <animation/sprite_frame.h>
#include <animation/sprite_frame_cache.h> #include <animation/sprite_frame_cache.h>
#include <animation/frame_property.h> #include <animation/tween_easing.h>
#include <animation/animation_frame.h> #include <animation/tween.h>
#include <animation/animation_clip.h>
#include <animation/animation_controller.h>
#include <animation/animation_cache.h>
#include <animation/interpolation_engine.h>
#include <animation/animated_sprite.h>
#include <animation/frame_renderer.h>
#include <animation/animation_event.h>
#include <animation/animation_node.h>
#include <animation/composite_animation.h>
#include <animation/ani_parser.h>
#include <animation/ani_binary_parser.h>
#include <animation/als_parser.h>
// UI // UI
#include <ui/widget.h> #include <ui/widget.h>

View File

@ -9,9 +9,6 @@
namespace extra2d { namespace extra2d {
// ============================================================================
// 鼠标按钮枚举
// ============================================================================
enum class MouseButton { enum class MouseButton {
Left = 0, Left = 0,
Right = 1, Right = 1,
@ -24,43 +21,27 @@ enum class MouseButton {
Count = 8 Count = 8
}; };
// ============================================================================
// Input 类 - 跨平台输入管理
// 支持: 键盘、鼠标、手柄、触摸屏
// ============================================================================
class Input { class Input {
public: public:
Input(); Input();
~Input(); ~Input();
// 初始化
void init(); void init();
void shutdown(); void shutdown();
// 每帧更新
void update(); void update();
// ------------------------------------------------------------------------
// 键盘输入
// ------------------------------------------------------------------------
bool isKeyDown(int keyCode) const; bool isKeyDown(int keyCode) const;
bool isKeyPressed(int keyCode) const; bool isKeyPressed(int keyCode) const;
bool isKeyReleased(int keyCode) const; bool isKeyReleased(int keyCode) const;
// ------------------------------------------------------------------------
// 手柄按钮
// ------------------------------------------------------------------------
bool isButtonDown(int button) const; bool isButtonDown(int button) const;
bool isButtonPressed(int button) const; bool isButtonPressed(int button) const;
bool isButtonReleased(int button) const; bool isButtonReleased(int button) const;
// 摇杆
Vec2 getLeftStick() const; Vec2 getLeftStick() const;
Vec2 getRightStick() const; Vec2 getRightStick() const;
// ------------------------------------------------------------------------
// 鼠标输入
// ------------------------------------------------------------------------
bool isMouseDown(MouseButton button) const; bool isMouseDown(MouseButton button) const;
bool isMousePressed(MouseButton button) const; bool isMousePressed(MouseButton button) const;
bool isMouseReleased(MouseButton button) const; bool isMouseReleased(MouseButton button) const;
@ -74,40 +55,34 @@ public:
void setMouseVisible(bool visible); void setMouseVisible(bool visible);
void setMouseLocked(bool locked); void setMouseLocked(bool locked);
// ------------------------------------------------------------------------
// 触摸屏 (Switch 原生支持PC 端模拟或禁用)
// ------------------------------------------------------------------------
bool isTouching() const { return touching_; } bool isTouching() const { return touching_; }
Vec2 getTouchPosition() const { return touchPosition_; } Vec2 getTouchPosition() const { return touchPosition_; }
int getTouchCount() const { return touchCount_; } int getTouchCount() const { return touchCount_; }
// ------------------------------------------------------------------------
// 便捷方法
// ------------------------------------------------------------------------
bool isAnyKeyDown() const; bool isAnyKeyDown() const;
bool isAnyMouseDown() const; bool isAnyMouseDown() const;
void onControllerAdded(int deviceIndex);
void onControllerRemoved(int instanceId);
void onMouseWheel(float x, float y);
private: private:
static constexpr int MAX_BUTTONS = SDL_CONTROLLER_BUTTON_MAX; static constexpr int MAX_BUTTONS = SDL_CONTROLLER_BUTTON_MAX;
static constexpr int MAX_KEYS = SDL_NUM_SCANCODES; static constexpr int MAX_KEYS = SDL_NUM_SCANCODES;
SDL_GameController *controller_; SDL_GameController *controller_;
// 键盘状态 (PC 端使用)
std::array<bool, MAX_KEYS> keysDown_; std::array<bool, MAX_KEYS> keysDown_;
std::array<bool, MAX_KEYS> prevKeysDown_; std::array<bool, MAX_KEYS> prevKeysDown_;
// 手柄按钮状态
std::array<bool, MAX_BUTTONS> buttonsDown_; std::array<bool, MAX_BUTTONS> buttonsDown_;
std::array<bool, MAX_BUTTONS> prevButtonsDown_; std::array<bool, MAX_BUTTONS> prevButtonsDown_;
// 摇杆状态
float leftStickX_; float leftStickX_;
float leftStickY_; float leftStickY_;
float rightStickX_; float rightStickX_;
float rightStickY_; float rightStickY_;
// 鼠标状态 (PC 端使用)
Vec2 mousePosition_; Vec2 mousePosition_;
Vec2 prevMousePosition_; Vec2 prevMousePosition_;
float mouseScroll_; float mouseScroll_;
@ -115,26 +90,15 @@ private:
std::array<bool, 8> mouseButtonsDown_; std::array<bool, 8> mouseButtonsDown_;
std::array<bool, 8> prevMouseButtonsDown_; std::array<bool, 8> prevMouseButtonsDown_;
// 触摸屏状态 (Switch 原生)
bool touching_; bool touching_;
bool prevTouching_; bool prevTouching_;
Vec2 touchPosition_; Vec2 touchPosition_;
Vec2 prevTouchPosition_; Vec2 prevTouchPosition_;
int touchCount_; int touchCount_;
// 映射键盘 keyCode 到 SDL GameController 按钮 (Switch 兼容模式)
SDL_GameControllerButton mapKeyToButton(int keyCode) const;
// 更新键盘状态
void updateKeyboard(); void updateKeyboard();
// 更新鼠标状态
void updateMouse(); void updateMouse();
// 更新手柄状态
void updateGamepad(); void updateGamepad();
// 更新触摸屏状态
void updateTouch(); void updateTouch();
}; };

View File

@ -7,6 +7,7 @@
#include <event/event_dispatcher.h> #include <event/event_dispatcher.h>
#include <graphics/render_backend.h> #include <graphics/render_backend.h>
#include <functional> #include <functional>
#include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
@ -16,6 +17,8 @@ namespace extra2d {
class Scene; class Scene;
class RenderBackend; class RenderBackend;
struct RenderCommand; struct RenderCommand;
class Tween;
enum class TweenEasing : uint8_t;
// ============================================================================ // ============================================================================
// 节点基类 - 场景图的基础 // 节点基类 - 场景图的基础
@ -159,6 +162,83 @@ public:
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
EventDispatcher &getEventDispatcher() { return eventDispatcher_; } EventDispatcher &getEventDispatcher() { return eventDispatcher_; }
// ------------------------------------------------------------------------
// Tween 动画系统
// ------------------------------------------------------------------------
/**
* @brief Tween
* @return Tween
*/
Tween& tween();
/**
* @brief Tween
*/
Tween& tween(const std::string &name);
/**
* @brief
*/
Tween &moveTo(const Vec2 &pos, float duration,
TweenEasing easing = static_cast<TweenEasing>(0));
/**
* @brief
*/
Tween &moveBy(const Vec2 &delta, float duration,
TweenEasing easing = static_cast<TweenEasing>(0));
/**
* @brief
*/
Tween &scaleTo(float scale, float duration,
TweenEasing easing = static_cast<TweenEasing>(0));
/**
* @brief
*/
Tween &scaleBy(float delta, float duration,
TweenEasing easing = static_cast<TweenEasing>(0));
/**
* @brief
*/
Tween &rotateTo(float degrees, float duration,
TweenEasing easing = static_cast<TweenEasing>(0));
/**
* @brief
*/
Tween &rotateBy(float degrees, float duration,
TweenEasing easing = static_cast<TweenEasing>(0));
/**
* @brief 1
*/
Tween &fadeIn(float duration, TweenEasing easing = static_cast<TweenEasing>(0));
/**
* @brief 0
*/
Tween &fadeOut(float duration, TweenEasing easing = static_cast<TweenEasing>(0));
/**
* @brief
*/
Tween &fadeTo(float opacity, float duration,
TweenEasing easing = static_cast<TweenEasing>(0));
/**
* @brief Tween
*/
void stopAllTweens();
/**
* @brief Tween
*/
void updateTweens(float dt);
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 内部方法 // 内部方法
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
@ -246,7 +326,9 @@ private:
bool visible_ = true; // 1 byte bool visible_ = true; // 1 byte
bool running_ = false; // 1 byte bool running_ = false; // 1 byte
bool spatialIndexed_ = true; // 1 byte bool spatialIndexed_ = true; // 1 byte
// 填充 2 bytes 到 8 字节对齐
// 13. Tween 动画列表
std::vector<std::shared_ptr<Tween>> tweens_;
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -1,44 +0,0 @@
#include <animation/als_parser.h>
#include <utils/logger.h>
namespace extra2d {
AlsParseResult AlsParser::parse(const std::string &filePath) {
AlsParseResult result;
// TODO: 实现实际的 ALS 文件格式解析
// 当前为框架实现,需要根据实际 ALS 文件格式补充解析逻辑
//
// 解析流程:
// 1. 读取 ALS 文件内容
// 2. 解析子动画列表:
// - 子动画 ANI 文件路径
// - 层级 zOrder
// - 偏移量
// 3. 组装 AlsLayerInfo 数组
result.success = true;
result.errorMessage =
"ALS parser framework ready - actual format parsing to be implemented";
return result;
}
AlsParseResult AlsParser::parseFromMemory(const std::string &content,
const std::string &basePath) {
AlsParseResult result;
// TODO: 从内存内容解析
result.success = true;
return result;
}
std::string AlsParser::resolvePath(const std::string &relativePath) const {
if (!basePath_.empty() && !relativePath.empty() && relativePath[0] != '/') {
return basePath_ + "/" + relativePath;
}
return relativePath;
}
} // namespace extra2d

View File

@ -1,313 +0,0 @@
#include <cassert>
#include <cstring>
#include <animation/ani_binary_parser.h>
#include <animation/sprite_frame_cache.h>
#include <utils/logger.h>
#include <fstream>
namespace extra2d {
namespace {
// 简易二进制缓冲区读取器
class BufferReader {
public:
BufferReader(const uint8_t *data, size_t length)
: data_(data), length_(length), pos_(0) {}
template <typename T> T read() {
if (pos_ + sizeof(T) > length_) {
return T{};
}
T value;
std::memcpy(&value, data_ + pos_, sizeof(T));
pos_ += sizeof(T);
return value;
}
std::string readAsciiString(int32_t len) {
if (len <= 0 || pos_ + static_cast<size_t>(len) > length_) {
return "";
}
std::string result(reinterpret_cast<const char *>(data_ + pos_), len);
pos_ += len;
return result;
}
bool hasRemaining() const { return pos_ < length_; }
size_t remaining() const { return length_ - pos_; }
private:
const uint8_t *data_;
size_t length_;
size_t pos_;
};
// 字符串转小写
void toLower(std::string &s) {
for (auto &c : s) {
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
}
}
} // anonymous namespace
AniParseResult AniBinaryParser::parse(const uint8_t *data, size_t length) {
AniParseResult result;
result.clip = AnimationClip::create();
if (!data || length < 4) {
result.success = false;
result.errorMessage = "Invalid binary ANI data";
return result;
}
BufferReader reader(data, length);
// 读取帧数和资源数
uint16_t frameCount = reader.read<uint16_t>();
uint16_t resourceCount = reader.read<uint16_t>();
// 读取精灵路径列表
std::vector<std::string> sprites;
sprites.reserve(resourceCount);
for (uint16_t i = 0; i < resourceCount; ++i) {
int32_t len = reader.read<int32_t>();
std::string path = reader.readAsciiString(len);
toLower(path);
sprites.push_back(std::move(path));
}
// 读取全局参数
uint16_t globalParamCount = reader.read<uint16_t>();
for (uint16_t j = 0; j < globalParamCount; ++j) {
uint16_t type = reader.read<uint16_t>();
switch (type) {
case static_cast<uint16_t>(AniNodeType::Loop):
if (reader.read<int8_t>()) {
result.clip->setLooping(true);
}
break;
case static_cast<uint16_t>(AniNodeType::Shadow):
if (reader.read<int8_t>()) {
result.clip->globalProperties().set(FramePropertyKey::Shadow, true);
}
break;
default:
break;
}
}
// 逐帧解析
for (uint16_t i = 0; i < frameCount; ++i) {
AnimationFrame frame;
// 碰撞盒
uint16_t boxCount = reader.read<uint16_t>();
for (uint16_t j = 0; j < boxCount; ++j) {
uint16_t boxType = reader.read<uint16_t>();
std::array<int32_t, 6> box;
for (int m = 0; m < 6; ++m) {
box[m] = reader.read<int32_t>();
}
if (boxType == static_cast<uint16_t>(AniNodeType::DamageBox)) {
frame.damageBoxes.push_back(box);
} else if (boxType == static_cast<uint16_t>(AniNodeType::AttackBox)) {
frame.attackBoxes.push_back(box);
}
}
// 图片 ID 和参数
uint16_t imgId = reader.read<uint16_t>();
uint16_t imgParam = reader.read<uint16_t>();
(void)imgParam;
if (imgId < sprites.size()) {
frame.texturePath = sprites[imgId];
frame.textureIndex = imgId;
std::string resolvedPath = resolvePath(frame.texturePath);
auto spriteFrame = SpriteFrameCache::getInstance().getOrCreateFromFile(
resolvedPath, frame.textureIndex);
frame.spriteFrame = spriteFrame;
}
// 位置
frame.offset = Vec2(static_cast<float>(reader.read<int32_t>()),
static_cast<float>(reader.read<int32_t>()));
// 帧属性
uint16_t propertyCount = reader.read<uint16_t>();
for (uint16_t m = 0; m < propertyCount; ++m) {
uint16_t propType = reader.read<uint16_t>();
AniNodeType nodeType = static_cast<AniNodeType>(propType);
switch (nodeType) {
case AniNodeType::Loop:
frame.properties.set(FramePropertyKey::Loop,
static_cast<bool>(reader.read<int8_t>()));
break;
case AniNodeType::Shadow:
frame.properties.set(FramePropertyKey::Shadow,
static_cast<bool>(reader.read<int8_t>()));
break;
case AniNodeType::Interpolation:
frame.properties.withInterpolation(
static_cast<bool>(reader.read<int8_t>()));
break;
case AniNodeType::Coord:
frame.properties.set(FramePropertyKey::Coord,
static_cast<int>(reader.read<uint16_t>()));
break;
case AniNodeType::ImageRate: {
float rateX = reader.read<float>();
float rateY = reader.read<float>();
frame.properties.withImageRate(Vec2(rateX, rateY));
break;
}
case AniNodeType::ImageRotate:
frame.properties.withImageRotate(
static_cast<float>(reader.read<int32_t>()));
break;
case AniNodeType::RGBA: {
uint32_t rgba = reader.read<uint32_t>();
uint8_t a = static_cast<uint8_t>((rgba >> 24) & 0xFF);
uint8_t r = static_cast<uint8_t>((rgba >> 16) & 0xFF);
uint8_t g = static_cast<uint8_t>((rgba >> 8) & 0xFF);
uint8_t b = static_cast<uint8_t>((rgba) & 0xFF);
frame.properties.withColorTint(Color::fromRGBA(r, g, b, a));
break;
}
case AniNodeType::GraphicEffect: {
uint16_t effectType = reader.read<uint16_t>();
frame.properties.set(FramePropertyKey::GraphicEffect,
static_cast<int>(effectType));
// MONOCHROME 额外读取 rgb
if (effectType == 5) {
reader.read<uint8_t>(); // r
reader.read<uint8_t>(); // g
reader.read<uint8_t>(); // b
}
// SPACEDISTORT 额外读取 pos
else if (effectType == 6) {
reader.read<uint16_t>(); // x
reader.read<uint16_t>(); // y
}
break;
}
case AniNodeType::Delay:
frame.delay = static_cast<float>(reader.read<int32_t>());
break;
case AniNodeType::DamageType:
frame.properties.set(FramePropertyKey::DamageType,
static_cast<int>(reader.read<uint16_t>()));
break;
case AniNodeType::PlaySound: {
int32_t len = reader.read<int32_t>();
std::string soundPath = reader.readAsciiString(len);
frame.properties.withPlaySound(resolvePath(soundPath));
break;
}
case AniNodeType::SetFlag:
frame.properties.withSetFlag(reader.read<int32_t>());
break;
case AniNodeType::FlipType:
frame.properties.set(FramePropertyKey::FlipType,
static_cast<int>(reader.read<uint16_t>()));
break;
case AniNodeType::LoopStart:
frame.properties.set(FramePropertyKey::LoopStart, true);
break;
case AniNodeType::LoopEnd:
frame.properties.set(FramePropertyKey::LoopEnd, reader.read<int32_t>());
break;
case AniNodeType::Clip: {
std::vector<int> clipRegion = {
static_cast<int>(reader.read<int16_t>()),
static_cast<int>(reader.read<int16_t>()),
static_cast<int>(reader.read<int16_t>()),
static_cast<int>(reader.read<int16_t>())};
frame.properties.set(FramePropertyKey::ClipRegion, clipRegion);
break;
}
default:
// 未知类型 - 跳过
break;
}
}
result.clip->addFrame(std::move(frame));
}
result.success = true;
return result;
}
AniParseResult AniBinaryParser::parseFromFile(const std::string &filePath) {
std::ifstream file(filePath, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
AniParseResult result;
result.success = false;
result.errorMessage = "Cannot open binary ANI file: " + filePath;
return result;
}
auto size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8_t> buffer(static_cast<size_t>(size));
file.read(reinterpret_cast<char *>(buffer.data()), size);
file.close();
// 提取基础路径
auto lastSlash = filePath.find_last_of("/\\");
if (lastSlash != std::string::npos && basePath_.empty()) {
basePath_ = filePath.substr(0, lastSlash);
}
auto result = parse(buffer.data(), buffer.size());
if (result.clip) {
result.clip->setSourcePath(filePath);
std::string name = (lastSlash != std::string::npos)
? filePath.substr(lastSlash + 1)
: filePath;
result.clip->setName(name);
}
return result;
}
std::string
AniBinaryParser::resolvePath(const std::string &relativePath) const {
std::string resolved = relativePath;
if (pathResolver_) {
resolved = pathResolver_(relativePath);
}
if (!basePath_.empty() && !resolved.empty() && resolved[0] != '/') {
if (resolved.size() < 2 || resolved[1] != ':') {
resolved = basePath_ + "/" + resolved;
}
}
return resolved;
}
} // namespace extra2d

View File

@ -1,428 +0,0 @@
#include <animation/ani_parser.h>
#include <animation/sprite_frame_cache.h>
#include <utils/logger.h>
#include <fstream>
#include <sstream>
namespace extra2d {
namespace {
std::string trim(const std::string &s) {
auto start = s.find_first_not_of(" \t\r\n");
if (start == std::string::npos)
return "";
auto end = s.find_last_not_of(" \t\r\n");
return s.substr(start, end - start + 1);
}
std::string stripBackticks(const std::string &s) {
std::string t = trim(s);
if (t.size() >= 2 && t.front() == '`' && t.back() == '`') {
return t.substr(1, t.size() - 2);
}
return t;
}
std::vector<std::string> splitWhitespace(const std::string &s) {
std::vector<std::string> tokens;
std::istringstream iss(s);
std::string token;
while (iss >> token) {
tokens.push_back(token);
}
return tokens;
}
bool lineStartsWith(const std::string &trimmed, const std::string &tag) {
return trimmed.find(tag) == 0;
}
std::string valueAfterTag(const std::string &trimmed, const std::string &tag) {
auto pos = trimmed.find(tag);
if (pos == std::string::npos)
return "";
return trim(trimmed.substr(pos + tag.size()));
}
// 读取标签后的值,如果同行没有则从流中读取下一行
std::string readValue(const std::string &trimmed, const std::string &tag,
std::istringstream &stream) {
std::string val = valueAfterTag(trimmed, tag);
if (val.empty()) {
std::string nextLine;
if (std::getline(stream, nextLine)) {
val = trim(nextLine);
}
}
return val;
}
} // anonymous namespace
AniParseResult AniParser::parse(const std::string &filePath) {
AniParseResult result;
std::ifstream file(filePath);
if (!file.is_open()) {
result.success = false;
result.errorMessage = "Cannot open ANI file: " + filePath;
return result;
}
std::string content((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
file.close();
// 提取基础路径
auto lastSlash = filePath.find_last_of("/\\");
std::string basePath;
if (lastSlash != std::string::npos) {
basePath = filePath.substr(0, lastSlash);
}
if (basePath_.empty() && !basePath.empty()) {
basePath_ = basePath;
}
result = parseFromMemory(content, basePath_.empty() ? basePath : basePath_);
if (result.clip) {
result.clip->setSourcePath(filePath);
std::string name = (lastSlash != std::string::npos)
? filePath.substr(lastSlash + 1)
: filePath;
result.clip->setName(name);
}
return result;
}
AniParseResult AniParser::parseFromMemory(const std::string &content,
const std::string &basePath) {
AniParseResult result;
result.clip = AnimationClip::create();
if (!basePath.empty() && basePath_.empty()) {
basePath_ = basePath;
}
std::istringstream stream(content);
std::string line;
AnimationFrame currentFrame;
bool inFrame = false;
bool hasCurrentFrame = false;
// [IMAGE] 标签的多行读取状态
bool pendingImage = false;
std::string imagePath;
auto finalizeFrame = [&]() {
if (!hasCurrentFrame)
return;
if (!currentFrame.texturePath.empty()) {
std::string resolvedPath = resolvePath(currentFrame.texturePath);
auto spriteFrame = SpriteFrameCache::getInstance().getOrCreateFromFile(
resolvedPath, currentFrame.textureIndex);
currentFrame.spriteFrame = spriteFrame;
}
result.clip->addFrame(std::move(currentFrame));
currentFrame = AnimationFrame();
hasCurrentFrame = false;
};
while (std::getline(stream, line)) {
std::string trimmed = trim(line);
if (trimmed.empty())
continue;
if (trimmed[0] == '#')
continue;
// [FRAME MAX]
if (lineStartsWith(trimmed, "[FRAME MAX]")) {
readValue(trimmed, "[FRAME MAX]", stream);
continue;
}
// [FRAMEXXX] - 新帧开始
if (trimmed.size() >= 7 && trimmed.substr(0, 6) == "[FRAME" &&
trimmed.back() == ']' && !lineStartsWith(trimmed, "[FRAME MAX]")) {
finalizeFrame();
currentFrame = AnimationFrame();
inFrame = true;
hasCurrentFrame = true;
pendingImage = false;
continue;
}
if (!inFrame) {
// 全局属性
if (lineStartsWith(trimmed, "[LOOP]")) {
result.clip->setLooping(true);
} else if (lineStartsWith(trimmed, "[SHADOW]")) {
result.clip->globalProperties().set(FramePropertyKey::Shadow, true);
}
continue;
}
// === 帧内属性解析 ===
// [IMAGE] - 图片路径和索引(跨行)
if (lineStartsWith(trimmed, "[IMAGE]")) {
std::string val = valueAfterTag(trimmed, "[IMAGE]");
if (!val.empty()) {
auto tokens = splitWhitespace(val);
imagePath = stripBackticks(tokens[0]);
if (tokens.size() >= 2) {
currentFrame.texturePath = imagePath;
currentFrame.textureIndex = std::stoi(tokens[1]);
pendingImage = false;
} else {
pendingImage = true;
}
} else {
pendingImage = true;
imagePath.clear();
}
continue;
}
// [IMAGE] 后续行
if (pendingImage) {
if (imagePath.empty()) {
imagePath = stripBackticks(trimmed);
} else {
currentFrame.texturePath = imagePath;
currentFrame.textureIndex = std::stoi(trimmed);
pendingImage = false;
}
continue;
}
// [IMAGE POS]
if (lineStartsWith(trimmed, "[IMAGE POS]")) {
std::string val = readValue(trimmed, "[IMAGE POS]", stream);
auto tokens = splitWhitespace(val);
if (tokens.size() >= 2) {
currentFrame.offset = Vec2(static_cast<float>(std::stoi(tokens[0])),
static_cast<float>(std::stoi(tokens[1])));
}
continue;
}
// [DELAY]
if (lineStartsWith(trimmed, "[DELAY]")) {
std::string val = readValue(trimmed, "[DELAY]", stream);
currentFrame.delay = static_cast<float>(std::stoi(trim(val)));
continue;
}
// [DAMAGE TYPE]
if (lineStartsWith(trimmed, "[DAMAGE TYPE]")) {
std::string val = readValue(trimmed, "[DAMAGE TYPE]", stream);
val = stripBackticks(val);
int damageType = 0;
if (val == "SUPERARMOR")
damageType = 1;
else if (val == "UNBREAKABLE")
damageType = 2;
currentFrame.properties.set(FramePropertyKey::DamageType, damageType);
continue;
}
// [DAMAGE BOX]
if (lineStartsWith(trimmed, "[DAMAGE BOX]")) {
std::string val = readValue(trimmed, "[DAMAGE BOX]", stream);
auto tokens = splitWhitespace(val);
if (tokens.size() >= 6) {
std::array<int32_t, 6> box;
for (int i = 0; i < 6; ++i) {
box[i] = std::stoi(tokens[i]);
}
currentFrame.damageBoxes.push_back(box);
}
continue;
}
// [ATTACK BOX]
if (lineStartsWith(trimmed, "[ATTACK BOX]")) {
std::string val = readValue(trimmed, "[ATTACK BOX]", stream);
auto tokens = splitWhitespace(val);
if (tokens.size() >= 6) {
std::array<int32_t, 6> box;
for (int i = 0; i < 6; ++i) {
box[i] = std::stoi(tokens[i]);
}
currentFrame.attackBoxes.push_back(box);
}
continue;
}
// [SET FLAG]
if (lineStartsWith(trimmed, "[SET FLAG]")) {
std::string val = readValue(trimmed, "[SET FLAG]", stream);
currentFrame.properties.withSetFlag(std::stoi(trim(val)));
continue;
}
// [PLAY SOUND]
if (lineStartsWith(trimmed, "[PLAY SOUND]")) {
std::string val = readValue(trimmed, "[PLAY SOUND]", stream);
currentFrame.properties.withPlaySound(resolvePath(stripBackticks(val)));
continue;
}
// [IMAGE RATE]
if (lineStartsWith(trimmed, "[IMAGE RATE]")) {
std::string val = readValue(trimmed, "[IMAGE RATE]", stream);
auto tokens = splitWhitespace(val);
if (tokens.size() >= 2) {
currentFrame.properties.withImageRate(
Vec2(std::stof(tokens[0]), std::stof(tokens[1])));
}
continue;
}
// [IMAGE ROTATE]
if (lineStartsWith(trimmed, "[IMAGE ROTATE]")) {
std::string val = readValue(trimmed, "[IMAGE ROTATE]", stream);
currentFrame.properties.withImageRotate(std::stof(trim(val)));
continue;
}
// [RGBA]
if (lineStartsWith(trimmed, "[RGBA]")) {
std::string val = readValue(trimmed, "[RGBA]", stream);
auto tokens = splitWhitespace(val);
if (tokens.size() >= 4) {
Color color =
Color::fromRGBA(static_cast<uint8_t>(std::stoi(tokens[0])),
static_cast<uint8_t>(std::stoi(tokens[1])),
static_cast<uint8_t>(std::stoi(tokens[2])),
static_cast<uint8_t>(std::stoi(tokens[3])));
currentFrame.properties.withColorTint(color);
}
continue;
}
// [INTERPOLATION]
if (lineStartsWith(trimmed, "[INTERPOLATION]")) {
currentFrame.properties.withInterpolation(true);
continue;
}
// [LOOP]
if (lineStartsWith(trimmed, "[LOOP]")) {
currentFrame.properties.set(FramePropertyKey::Loop, true);
continue;
}
// [SHADOW]
if (lineStartsWith(trimmed, "[SHADOW]")) {
currentFrame.properties.set(FramePropertyKey::Shadow, true);
continue;
}
// [FLIP TYPE]
if (lineStartsWith(trimmed, "[FLIP TYPE]")) {
std::string val = readValue(trimmed, "[FLIP TYPE]", stream);
val = stripBackticks(val);
int flipType = 0;
if (val == "HORIZON")
flipType = 1;
else if (val == "VERTICAL")
flipType = 2;
else if (val == "ALL")
flipType = 3;
else
flipType = std::stoi(val);
currentFrame.properties.set(FramePropertyKey::FlipType, flipType);
continue;
}
// [COORD]
if (lineStartsWith(trimmed, "[COORD]")) {
std::string val = readValue(trimmed, "[COORD]", stream);
currentFrame.properties.set(FramePropertyKey::Coord,
std::stoi(trim(val)));
continue;
}
// [GRAPHIC EFFECT]
if (lineStartsWith(trimmed, "[GRAPHIC EFFECT]")) {
std::string val = readValue(trimmed, "[GRAPHIC EFFECT]", stream);
val = stripBackticks(val);
int effectType = 0;
if (val == "DODGE")
effectType = 1;
else if (val == "LINEARDODGE")
effectType = 2;
else if (val == "DARK")
effectType = 3;
else if (val == "XOR")
effectType = 4;
else if (val == "MONOCHROME")
effectType = 5;
else if (val == "SPACEDISTORT")
effectType = 6;
else
effectType = std::stoi(val);
currentFrame.properties.set(FramePropertyKey::GraphicEffect, effectType);
continue;
}
// [CLIP]
if (lineStartsWith(trimmed, "[CLIP]")) {
std::string val = readValue(trimmed, "[CLIP]", stream);
auto tokens = splitWhitespace(val);
if (tokens.size() >= 4) {
std::vector<int> clipRegion = {
std::stoi(tokens[0]), std::stoi(tokens[1]), std::stoi(tokens[2]),
std::stoi(tokens[3])};
currentFrame.properties.set(FramePropertyKey::ClipRegion, clipRegion);
}
continue;
}
// [LOOP START]
if (lineStartsWith(trimmed, "[LOOP START]")) {
currentFrame.properties.set(FramePropertyKey::LoopStart, true);
continue;
}
// [LOOP END]
if (lineStartsWith(trimmed, "[LOOP END]")) {
std::string val = readValue(trimmed, "[LOOP END]", stream);
currentFrame.properties.set(FramePropertyKey::LoopEnd,
std::stoi(trim(val)));
continue;
}
// 未识别标签 - 静默忽略
}
// 保存最后一帧
finalizeFrame();
result.success = true;
return result;
}
std::string AniParser::resolvePath(const std::string &relativePath) const {
std::string resolved = relativePath;
if (pathResolver_) {
resolved = pathResolver_(relativePath);
}
if (!basePath_.empty() && !resolved.empty() && resolved[0] != '/') {
if (resolved.size() < 2 || resolved[1] != ':') {
resolved = basePath_ + "/" + resolved;
}
}
return resolved;
}
} // namespace extra2d

View File

@ -1,290 +0,0 @@
#include <animation/animated_sprite.h>
#include <animation/interpolation_engine.h>
namespace extra2d {
const std::vector<std::array<int32_t, 6>> AnimatedSprite::emptyBoxes_;
const std::string AnimatedSprite::emptyString_;
AnimatedSprite::AnimatedSprite() {
controller_.setFrameChangeCallback(
[this](size_t oldIdx, size_t newIdx, const AnimationFrame &frame) {
onFrameChanged(oldIdx, newIdx, frame);
});
}
// ------ 静态工厂 ------
Ptr<AnimatedSprite> AnimatedSprite::create() {
return makePtr<AnimatedSprite>();
}
Ptr<AnimatedSprite> AnimatedSprite::create(Ptr<AnimationClip> clip) {
auto sprite = makePtr<AnimatedSprite>();
sprite->setAnimationClip(std::move(clip));
return sprite;
}
Ptr<AnimatedSprite> AnimatedSprite::create(const std::string &aniFilePath) {
auto sprite = makePtr<AnimatedSprite>();
sprite->loadAnimation(aniFilePath);
return sprite;
}
// ------ 动画绑定 ------
void AnimatedSprite::setAnimationClip(Ptr<AnimationClip> clip) {
controller_.setClip(clip);
if (clip && !clip->empty()) {
applyFrame(clip->getFrame(0));
}
}
void AnimatedSprite::loadAnimation(const std::string &aniFilePath) {
auto clip = AnimationCache::getInstance().loadClip(aniFilePath);
if (clip) {
setAnimationClip(clip);
}
}
Ptr<AnimationClip> AnimatedSprite::getAnimationClip() const {
return controller_.getClip();
}
// ------ 动画字典 ------
void AnimatedSprite::addAnimation(const std::string &name,
Ptr<AnimationClip> clip) {
if (clip) {
animations_[name] = std::move(clip);
}
}
void AnimatedSprite::play(const std::string &name, bool loop) {
auto it = animations_.find(name);
if (it == animations_.end())
return;
currentAnimationName_ = name;
// 精灵图动画不应覆盖节点的 position/scale/rotation
applyFrameTransform_ = false;
setAnimationClip(it->second);
setLooping(loop);
play();
}
bool AnimatedSprite::hasAnimation(const std::string &name) const {
return animations_.find(name) != animations_.end();
}
Ptr<AnimationClip> AnimatedSprite::getAnimation(const std::string &name) const {
auto it = animations_.find(name);
if (it != animations_.end())
return it->second;
return nullptr;
}
const std::string &AnimatedSprite::getCurrentAnimationName() const {
return currentAnimationName_;
}
// ------ 播放控制 ------
void AnimatedSprite::play() { controller_.play(); }
void AnimatedSprite::pause() { controller_.pause(); }
void AnimatedSprite::resume() { controller_.resume(); }
void AnimatedSprite::stop() { controller_.stop(); }
void AnimatedSprite::reset() { controller_.reset(); }
bool AnimatedSprite::isPlaying() const { return controller_.isPlaying(); }
bool AnimatedSprite::isPaused() const { return controller_.isPaused(); }
bool AnimatedSprite::isStopped() const { return controller_.isStopped(); }
// ------ 属性控制 ------
void AnimatedSprite::setLooping(bool loop) { controller_.setLooping(loop); }
bool AnimatedSprite::isLooping() const { return controller_.isLooping(); }
void AnimatedSprite::setPlaybackSpeed(float speed) {
controller_.setPlaybackSpeed(speed);
}
float AnimatedSprite::getPlaybackSpeed() const {
return controller_.getPlaybackSpeed();
}
// ------ 帧控制 ------
void AnimatedSprite::setFrameIndex(size_t index) {
controller_.setFrameIndex(index);
}
size_t AnimatedSprite::getCurrentFrameIndex() const {
return controller_.getCurrentFrameIndex();
}
size_t AnimatedSprite::getTotalFrames() const {
return controller_.getTotalFrames();
}
void AnimatedSprite::nextFrame() { controller_.nextFrame(); }
void AnimatedSprite::prevFrame() { controller_.prevFrame(); }
// ------ 帧范围限制 ------
void AnimatedSprite::setFrameRange(int start, int end) {
frameRangeStart_ = start;
frameRangeEnd_ = end;
// 确保当前帧在新的范围内
auto clip = controller_.getClip();
if (clip && !clip->empty()) {
size_t currentFrame = controller_.getCurrentFrameIndex();
size_t minFrame = static_cast<size_t>(start);
size_t maxFrame =
(end < 0) ? clip->getFrameCount() - 1 : static_cast<size_t>(end);
if (currentFrame < minFrame || currentFrame > maxFrame) {
controller_.setFrameIndex(minFrame);
}
}
}
std::pair<int, int> AnimatedSprite::getFrameRange() const {
return {frameRangeStart_, frameRangeEnd_};
}
void AnimatedSprite::clearFrameRange() {
frameRangeStart_ = 0;
frameRangeEnd_ = -1;
}
bool AnimatedSprite::hasFrameRange() const { return frameRangeEnd_ >= 0; }
// ------ 回调 ------
void AnimatedSprite::setCompletionCallback(
AnimationController::CompletionCallback cb) {
controller_.setCompletionCallback(std::move(cb));
}
void AnimatedSprite::setKeyframeCallback(
AnimationController::KeyframeCallback cb) {
controller_.setKeyframeCallback(std::move(cb));
}
void AnimatedSprite::setSoundTriggerCallback(
AnimationController::SoundTriggerCallback cb) {
controller_.setSoundTriggerCallback(std::move(cb));
}
// ------ 碰撞盒访问 ------
const std::vector<std::array<int32_t, 6>> &
AnimatedSprite::getCurrentDamageBoxes() const {
auto clip = controller_.getClip();
if (!clip || clip->empty())
return emptyBoxes_;
return clip->getFrame(controller_.getCurrentFrameIndex()).damageBoxes;
}
const std::vector<std::array<int32_t, 6>> &
AnimatedSprite::getCurrentAttackBoxes() const {
auto clip = controller_.getClip();
if (!clip || clip->empty())
return emptyBoxes_;
return clip->getFrame(controller_.getCurrentFrameIndex()).attackBoxes;
}
// ------ 生命周期 ------
void AnimatedSprite::onEnter() {
Sprite::onEnter();
if (autoPlay_ && controller_.getClip() && !controller_.getClip()->empty()) {
play();
}
}
void AnimatedSprite::onUpdate(float dt) {
Sprite::onUpdate(dt);
// 保存更新前的帧索引
size_t prevFrameIdx = controller_.getCurrentFrameIndex();
controller_.update(dt);
// 应用帧范围限制
if (hasFrameRange() && controller_.isPlaying()) {
size_t currentFrame = controller_.getCurrentFrameIndex();
size_t minFrame = static_cast<size_t>(frameRangeStart_);
size_t maxFrame = static_cast<size_t>(frameRangeEnd_);
auto clip = controller_.getClip();
if (clip && !clip->empty()) {
// 确保范围有效
if (maxFrame >= clip->getFrameCount()) {
maxFrame = clip->getFrameCount() - 1;
}
// 如果超出范围,回到起始帧
if (currentFrame < minFrame || currentFrame > maxFrame) {
controller_.setFrameIndex(minFrame);
}
}
}
// 插值处理
if (controller_.isInterpolating()) {
auto clip = controller_.getClip();
if (clip) {
size_t idx = controller_.getCurrentFrameIndex();
if (idx + 1 < clip->getFrameCount()) {
auto props = InterpolationEngine::interpolate(
clip->getFrame(idx), clip->getFrame(idx + 1),
controller_.getInterpolationFactor());
// 仅更新插值属性,不覆盖帧纹理
Sprite::setColor(props.color);
}
}
}
}
// ------ 内部方法 ------
void AnimatedSprite::onFrameChanged(size_t /*oldIdx*/, size_t /*newIdx*/,
const AnimationFrame &frame) {
applyFrame(frame);
}
void AnimatedSprite::applyFrame(const AnimationFrame &frame) {
// 更新纹理
if (frame.spriteFrame && frame.spriteFrame->isValid()) {
Sprite::setTexture(frame.spriteFrame->getTexture());
Sprite::setTextureRect(frame.spriteFrame->getRect());
}
// 帧变换仅在 ANI 动画模式下应用;精灵图模式跳过,避免覆盖节点世界坐标
if (applyFrameTransform_) {
// 应用帧偏移(作为精灵位置)
Node::setPosition(frame.offset);
// 应用缩放
Vec2 scale = frame.getEffectiveScale();
Node::setScale(scale);
// 应用旋转
float rotation = frame.getEffectiveRotation();
Node::setRotation(rotation);
// 应用翻转
auto flipType = frame.properties.get<int>(FramePropertyKey::FlipType);
if (flipType.has_value()) {
int flip = flipType.value();
Sprite::setFlipX(flip == 1 || flip == 3);
Sprite::setFlipY(flip == 2 || flip == 3);
} else {
Sprite::setFlipX(false);
Sprite::setFlipY(false);
}
}
// 颜色始终应用
Color color = frame.getEffectiveColor();
Sprite::setColor(color);
}
} // namespace extra2d

View File

@ -1,103 +0,0 @@
#include <animation/ani_binary_parser.h>
#include <animation/ani_parser.h>
#include <animation/animation_cache.h>
#include <utils/logger.h>
#include <fstream>
namespace extra2d {
namespace {
// 检测文件是否为文本格式 ANI
// 文本格式以 '#' 或 '[' 开头(跳过空白后)
bool isTextFormat(const std::string &filePath) {
std::ifstream file(filePath, std::ios::binary);
if (!file.is_open())
return false;
// 读取前几个字节判断
char buf[16] = {};
file.read(buf, sizeof(buf));
auto bytesRead = file.gcount();
file.close();
// 跳过 BOM 和空白
size_t pos = 0;
// UTF-8 BOM
if (bytesRead >= 3 && static_cast<uint8_t>(buf[0]) == 0xEF &&
static_cast<uint8_t>(buf[1]) == 0xBB &&
static_cast<uint8_t>(buf[2]) == 0xBF) {
pos = 3;
}
// 跳过空白
while (pos < static_cast<size_t>(bytesRead) &&
(buf[pos] == ' ' || buf[pos] == '\t' || buf[pos] == '\r' ||
buf[pos] == '\n')) {
++pos;
}
if (pos >= static_cast<size_t>(bytesRead))
return false;
// 文本 ANI 文件以 '#' (注释/PVF_File) 或 '[' (标签) 开头
return buf[pos] == '#' || buf[pos] == '[';
}
} // anonymous namespace
Ptr<AnimationClip> AnimationCache::loadClip(const std::string &aniFilePath) {
// 先检查缓存
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = clips_.find(aniFilePath);
if (it != clips_.end()) {
return it->second;
}
}
// 提取基础路径
std::string basePath;
auto lastSlash = aniFilePath.find_last_of("/\\");
if (lastSlash != std::string::npos) {
basePath = aniFilePath.substr(0, lastSlash);
}
AniParseResult result;
if (isTextFormat(aniFilePath)) {
// 文本格式
AniParser parser;
if (pathResolver_) {
parser.setPathResolver(pathResolver_);
}
if (!basePath.empty()) {
parser.setBasePath(basePath);
}
result = parser.parse(aniFilePath);
} else {
// 二进制格式
AniBinaryParser parser;
if (pathResolver_) {
parser.setPathResolver(pathResolver_);
}
if (!basePath.empty()) {
parser.setBasePath(basePath);
}
result = parser.parseFromFile(aniFilePath);
}
if (!result.success || !result.clip) {
return nullptr;
}
// 添加到缓存
{
std::lock_guard<std::mutex> lock(mutex_);
clips_[aniFilePath] = result.clip;
}
return result.clip;
}
} // namespace extra2d

View File

@ -1,8 +0,0 @@
#include <animation/animation_clip.h>
namespace extra2d {
// AnimationClip 的实现全部在头文件中以内联方式完成
// 此文件保留用于未来可能需要的非内联实现
} // namespace extra2d

View File

@ -1,209 +0,0 @@
#include <cassert>
#include <animation/animation_controller.h>
namespace extra2d {
void AnimationController::setClip(Ptr<AnimationClip> clip) {
clip_ = std::move(clip);
currentFrameIndex_ = 0;
accumulatedTime_ = 0.0f;
interpolating_ = false;
interpolationFactor_ = 0.0f;
state_ = AnimPlayState::Stopped;
}
void AnimationController::play() {
if (!clip_ || clip_->empty())
return;
state_ = AnimPlayState::Playing;
}
void AnimationController::pause() {
if (state_ == AnimPlayState::Playing) {
state_ = AnimPlayState::Paused;
}
}
void AnimationController::resume() {
if (state_ == AnimPlayState::Paused) {
state_ = AnimPlayState::Playing;
}
}
void AnimationController::stop() {
state_ = AnimPlayState::Stopped;
accumulatedTime_ = 0.0f;
interpolating_ = false;
interpolationFactor_ = 0.0f;
}
void AnimationController::reset() {
stop();
if (clip_ && !clip_->empty()) {
advanceFrame(0);
}
}
void AnimationController::setFrameIndex(size_t index) {
if (!clip_ || index >= clip_->getFrameCount())
return;
accumulatedTime_ = 0.0f;
advanceFrame(index);
}
void AnimationController::nextFrame() {
if (!clip_ || clip_->empty())
return;
size_t next = currentFrameIndex_ + 1;
if (next >= clip_->getFrameCount()) {
if (isLooping()) {
next = 0;
} else {
return;
}
}
accumulatedTime_ = 0.0f;
advanceFrame(next);
}
void AnimationController::prevFrame() {
if (!clip_ || clip_->empty())
return;
if (currentFrameIndex_ > 0) {
accumulatedTime_ = 0.0f;
advanceFrame(currentFrameIndex_ - 1);
} else if (isLooping()) {
accumulatedTime_ = 0.0f;
advanceFrame(clip_->getFrameCount() - 1);
}
}
void AnimationController::update(float dt) {
if (state_ != AnimPlayState::Playing)
return;
if (!clip_ || clip_->empty())
return;
// 累加时间(转换为毫秒)
float dt_ms = dt * 1000.0f * playbackSpeed_;
accumulatedTime_ += dt_ms;
const AnimationFrame &currentFrame = clip_->getFrame(currentFrameIndex_);
float frameDelay = currentFrame.delay;
// 更新插值状态
updateInterpolation();
// 循环处理:支持一次跳过多帧(与原始 ANI 系统行为一致)
while (accumulatedTime_ >= frameDelay) {
accumulatedTime_ -= frameDelay;
size_t totalFrames = clip_->getFrameCount();
if (currentFrameIndex_ < totalFrames - 1) {
// 推进到下一帧
advanceFrame(currentFrameIndex_ + 1);
} else {
// 最后一帧播放完毕
if (isLooping()) {
advanceFrame(0);
} else {
// 动画结束
state_ = AnimPlayState::Stopped;
if (onComplete_) {
onComplete_();
}
return;
}
}
// 更新下一帧的延迟
frameDelay = clip_->getFrame(currentFrameIndex_).delay;
}
// 更新插值因子
updateInterpolation();
}
size_t AnimationController::getTotalFrames() const {
return clip_ ? clip_->getFrameCount() : 0;
}
const AnimationFrame &AnimationController::getCurrentFrame() const {
assert(clip_ && currentFrameIndex_ < clip_->getFrameCount());
return clip_->getFrame(currentFrameIndex_);
}
bool AnimationController::isLooping() const {
if (hasLoopOverride_)
return loopOverride_;
return clip_ ? clip_->isLooping() : false;
}
void AnimationController::setLooping(bool loop) {
hasLoopOverride_ = true;
loopOverride_ = loop;
}
void AnimationController::advanceFrame(size_t newIndex) {
if (!clip_ || newIndex >= clip_->getFrameCount())
return;
size_t oldIndex = currentFrameIndex_;
currentFrameIndex_ = newIndex;
const AnimationFrame &frame = clip_->getFrame(newIndex);
// 触发帧变更回调
if (onFrameChange_) {
onFrameChange_(oldIndex, newIndex, frame);
}
// 处理帧属性(关键帧、音效等)
processFrameProperties(frame);
}
void AnimationController::processFrameProperties(const AnimationFrame &frame) {
const auto &props = frame.properties;
// 关键帧回调
if (props.has(FramePropertyKey::SetFlag)) {
auto flagIndex = props.get<int>(FramePropertyKey::SetFlag);
if (flagIndex.has_value() && onKeyframe_) {
onKeyframe_(flagIndex.value());
}
}
// 音效触发
if (props.has(FramePropertyKey::PlaySound)) {
auto soundPath = props.get<std::string>(FramePropertyKey::PlaySound);
if (soundPath.has_value() && onSoundTrigger_) {
onSoundTrigger_(soundPath.value());
}
}
}
void AnimationController::updateInterpolation() {
if (!clip_ || clip_->empty()) {
interpolating_ = false;
interpolationFactor_ = 0.0f;
return;
}
const AnimationFrame &currentFrame = clip_->getFrame(currentFrameIndex_);
if (currentFrame.hasInterpolation() &&
currentFrameIndex_ + 1 < clip_->getFrameCount()) {
interpolating_ = true;
float frameDelay = currentFrame.delay;
interpolationFactor_ =
(frameDelay > 0.0f)
? math::clamp(accumulatedTime_ / frameDelay, 0.0f, 1.0f)
: 0.0f;
} else {
interpolating_ = false;
interpolationFactor_ = 0.0f;
}
}
} // namespace extra2d

View File

@ -1,8 +0,0 @@
#include <animation/animation_frame.h>
namespace extra2d {
// AnimationFrame 的实现全部在头文件中以内联方式完成
// 此文件保留用于未来可能需要的非内联实现
} // namespace extra2d

View File

@ -1,266 +0,0 @@
#include <animation/animation_node.h>
#include <animation/interpolation_engine.h>
namespace extra2d {
const std::vector<std::array<int32_t, 6>> AnimationNode::emptyBoxes_;
// ============================================================================
// 构造
// ============================================================================
AnimationNode::AnimationNode() { setupControllerCallbacks(); }
// ============================================================================
// 静态工厂
// ============================================================================
Ptr<AnimationNode> AnimationNode::create() { return makePtr<AnimationNode>(); }
Ptr<AnimationNode> AnimationNode::create(Ptr<AnimationClip> clip) {
auto node = makePtr<AnimationNode>();
node->setClip(std::move(clip));
return node;
}
Ptr<AnimationNode> AnimationNode::create(const std::string &aniFilePath) {
auto node = makePtr<AnimationNode>();
node->loadFromFile(aniFilePath);
return node;
}
// ============================================================================
// 动画数据
// ============================================================================
void AnimationNode::setClip(Ptr<AnimationClip> clip) {
controller_.setClip(clip);
if (clip && !clip->empty()) {
// 预加载所有帧的 SpriteFrame
std::vector<AnimationFrame> frames;
frames.reserve(clip->getFrameCount());
for (size_t i = 0; i < clip->getFrameCount(); ++i) {
frames.push_back(clip->getFrame(i));
}
frameRenderer_.preloadFrames(frames);
} else {
frameRenderer_.releaseFrames();
}
}
Ptr<AnimationClip> AnimationNode::getClip() const {
return controller_.getClip();
}
bool AnimationNode::loadFromFile(const std::string &aniFilePath) {
auto clip = AnimationCache::getInstance().loadClip(aniFilePath);
if (clip) {
setClip(clip);
return true;
}
return false;
}
// ============================================================================
// 播放控制
// ============================================================================
void AnimationNode::play() { controller_.play(); }
void AnimationNode::pause() { controller_.pause(); }
void AnimationNode::resume() { controller_.resume(); }
void AnimationNode::stop() { controller_.stop(); }
void AnimationNode::reset() { controller_.reset(); }
bool AnimationNode::isPlaying() const { return controller_.isPlaying(); }
bool AnimationNode::isPaused() const { return controller_.isPaused(); }
bool AnimationNode::isStopped() const { return controller_.isStopped(); }
void AnimationNode::setPlaybackSpeed(float speed) {
controller_.setPlaybackSpeed(speed);
}
float AnimationNode::getPlaybackSpeed() const {
return controller_.getPlaybackSpeed();
}
void AnimationNode::setLooping(bool loop) { controller_.setLooping(loop); }
bool AnimationNode::isLooping() const { return controller_.isLooping(); }
// ============================================================================
// 帧控制
// ============================================================================
void AnimationNode::setFrameIndex(size_t index) {
controller_.setFrameIndex(index);
}
size_t AnimationNode::getCurrentFrameIndex() const {
return controller_.getCurrentFrameIndex();
}
size_t AnimationNode::getTotalFrames() const {
return controller_.getTotalFrames();
}
// ============================================================================
// 事件回调
// ============================================================================
void AnimationNode::setKeyframeCallback(KeyframeHitCallback callback) {
controller_.setKeyframeCallback(
[this, cb = std::move(callback)](int flagIndex) {
if (cb)
cb(flagIndex);
AnimationEvent evt;
evt.type = AnimationEventType::KeyframeHit;
evt.frameIndex = controller_.getCurrentFrameIndex();
evt.keyframeFlag = flagIndex;
evt.source = this;
dispatchEvent(evt);
});
}
void AnimationNode::setCompletionCallback(AnimationCompleteCallback callback) {
controller_.setCompletionCallback([this, cb = std::move(callback)]() {
if (cb)
cb();
AnimationEvent evt;
evt.type = AnimationEventType::AnimationEnd;
evt.frameIndex = controller_.getCurrentFrameIndex();
evt.source = this;
dispatchEvent(evt);
});
}
void AnimationNode::setFrameChangeCallback(
AnimationController::FrameChangeCallback callback) {
// 保存外部回调,在 setupControllerCallbacks 中已设置内部回调
// 需要重新绑定,将两者合并
controller_.setFrameChangeCallback(
[this, cb = std::move(callback)](size_t oldIdx, size_t newIdx,
const AnimationFrame &frame) {
if (cb)
cb(oldIdx, newIdx, frame);
AnimationEvent evt;
evt.type = AnimationEventType::FrameChanged;
evt.frameIndex = newIdx;
evt.previousFrameIndex = oldIdx;
evt.source = this;
dispatchEvent(evt);
});
}
void AnimationNode::addEventListener(AnimationEventCallback callback) {
eventListeners_.push_back(std::move(callback));
}
// ============================================================================
// 视觉属性
// ============================================================================
void AnimationNode::setTintColor(const Color &color) { tintColor_ = color; }
// ============================================================================
// 碰撞盒
// ============================================================================
const std::vector<std::array<int32_t, 6>> &
AnimationNode::getCurrentDamageBoxes() const {
auto clip = controller_.getClip();
if (!clip || clip->empty())
return emptyBoxes_;
return clip->getFrame(controller_.getCurrentFrameIndex()).damageBoxes;
}
const std::vector<std::array<int32_t, 6>> &
AnimationNode::getCurrentAttackBoxes() const {
auto clip = controller_.getClip();
if (!clip || clip->empty())
return emptyBoxes_;
return clip->getFrame(controller_.getCurrentFrameIndex()).attackBoxes;
}
// ============================================================================
// 查询
// ============================================================================
Size AnimationNode::getMaxFrameSize() const {
return frameRenderer_.getMaxFrameSize();
}
Rect AnimationNode::getBoundingBox() const {
Size size = frameRenderer_.getMaxFrameSize();
Vec2 pos = getPosition();
Vec2 anchor = getAnchor();
return Rect{{pos.x - size.width * anchor.x, pos.y - size.height * anchor.y},
size};
}
// ============================================================================
// 生命周期
// ============================================================================
void AnimationNode::onEnter() {
Node::onEnter();
if (autoPlay_ && controller_.getClip() && !controller_.getClip()->empty()) {
play();
}
}
void AnimationNode::onExit() { Node::onExit(); }
void AnimationNode::onUpdate(float dt) {
Node::onUpdate(dt);
controller_.update(dt);
}
void AnimationNode::onDraw(RenderBackend &renderer) {
auto clip = controller_.getClip();
if (!clip || clip->empty())
return;
size_t idx = controller_.getCurrentFrameIndex();
const auto &frame = clip->getFrame(idx);
Vec2 pos = getPosition();
if (controller_.isInterpolating() && idx + 1 < clip->getFrameCount()) {
auto props = InterpolationEngine::interpolate(
frame, clip->getFrame(idx + 1), controller_.getInterpolationFactor());
frameRenderer_.renderInterpolated(renderer, frame, idx, props, pos,
getOpacity(), tintColor_, flipX_, flipY_);
} else {
frameRenderer_.renderFrame(renderer, frame, idx, pos, getOpacity(),
tintColor_, flipX_, flipY_);
}
}
// ============================================================================
// 内部方法
// ============================================================================
void AnimationNode::setupControllerCallbacks() {
controller_.setFrameChangeCallback(
[this](size_t oldIdx, size_t newIdx, const AnimationFrame &frame) {
AnimationEvent evt;
evt.type = AnimationEventType::FrameChanged;
evt.frameIndex = newIdx;
evt.previousFrameIndex = oldIdx;
evt.source = this;
dispatchEvent(evt);
});
controller_.setSoundTriggerCallback([this](const std::string &path) {
AnimationEvent evt;
evt.type = AnimationEventType::SoundTrigger;
evt.frameIndex = controller_.getCurrentFrameIndex();
evt.soundPath = path;
evt.source = this;
dispatchEvent(evt);
});
}
void AnimationNode::dispatchEvent(const AnimationEvent &event) {
for (auto &listener : eventListeners_) {
if (listener)
listener(event);
}
}
} // namespace extra2d

View File

@ -1,200 +0,0 @@
#include <animation/animation_cache.h>
#include <animation/composite_animation.h>
namespace extra2d {
// ============================================================================
// 静态工厂
// ============================================================================
Ptr<CompositeAnimation> CompositeAnimation::create() {
return makePtr<CompositeAnimation>();
}
Ptr<CompositeAnimation>
CompositeAnimation::create(const std::string &alsFilePath) {
auto comp = makePtr<CompositeAnimation>();
comp->loadFromFile(alsFilePath);
return comp;
}
// ============================================================================
// 加载 ALS 文件
// ============================================================================
bool CompositeAnimation::loadFromFile(const std::string &alsFilePath) {
AlsParser parser;
auto result = parser.parse(alsFilePath);
if (!result.success)
return false;
// 清除现有图层
for (auto &entry : layers_) {
if (entry.node) {
removeChild(entry.node);
}
}
layers_.clear();
// 创建每个图层
for (const auto &layer : result.layers) {
auto node = AnimationNode::create();
if (node->loadFromFile(layer.aniPath)) {
node->setPosition(layer.offset);
addLayer(node, layer.zOrder);
}
}
return !layers_.empty();
}
// ============================================================================
// 图层管理
// ============================================================================
void CompositeAnimation::addLayer(Ptr<AnimationNode> node, int zOrder) {
if (!node)
return;
node->setZOrder(zOrder);
addChild(node);
layers_.push_back({node, zOrder});
}
void CompositeAnimation::removeLayer(size_t index) {
if (index >= layers_.size())
return;
auto &entry = layers_[index];
if (entry.node) {
removeChild(entry.node);
}
layers_.erase(layers_.begin() + static_cast<ptrdiff_t>(index));
}
Ptr<AnimationNode> CompositeAnimation::getLayer(size_t index) const {
if (index >= layers_.size())
return nullptr;
return layers_[index].node;
}
Ptr<AnimationNode> CompositeAnimation::getMainLayer() const {
if (layers_.empty())
return nullptr;
return layers_[0].node;
}
size_t CompositeAnimation::getLayerCount() const { return layers_.size(); }
// ============================================================================
// 统一播放控制
// ============================================================================
void CompositeAnimation::play() {
for (auto &entry : layers_) {
if (entry.node)
entry.node->play();
}
}
void CompositeAnimation::pause() {
for (auto &entry : layers_) {
if (entry.node)
entry.node->pause();
}
}
void CompositeAnimation::resume() {
for (auto &entry : layers_) {
if (entry.node)
entry.node->resume();
}
}
void CompositeAnimation::stop() {
for (auto &entry : layers_) {
if (entry.node)
entry.node->stop();
}
}
void CompositeAnimation::reset() {
for (auto &entry : layers_) {
if (entry.node)
entry.node->reset();
}
}
void CompositeAnimation::setPlaybackSpeed(float speed) {
for (auto &entry : layers_) {
if (entry.node)
entry.node->setPlaybackSpeed(speed);
}
}
void CompositeAnimation::setLooping(bool loop) {
for (auto &entry : layers_) {
if (entry.node)
entry.node->setLooping(loop);
}
}
bool CompositeAnimation::isPlaying() const {
auto main = getMainLayer();
return main ? main->isPlaying() : false;
}
bool CompositeAnimation::isStopped() const {
auto main = getMainLayer();
return main ? main->isStopped() : true;
}
// ============================================================================
// 事件回调(绑定到主图层)
// ============================================================================
void CompositeAnimation::setKeyframeCallback(KeyframeHitCallback callback) {
auto main = getMainLayer();
if (main)
main->setKeyframeCallback(std::move(callback));
}
void CompositeAnimation::setCompletionCallback(
AnimationCompleteCallback callback) {
auto main = getMainLayer();
if (main)
main->setCompletionCallback(std::move(callback));
}
void CompositeAnimation::addEventListener(AnimationEventCallback callback) {
auto main = getMainLayer();
if (main)
main->addEventListener(std::move(callback));
}
// ============================================================================
// 视觉属性(应用到所有图层)
// ============================================================================
void CompositeAnimation::setTintColor(const Color &color) {
for (auto &entry : layers_) {
if (entry.node)
entry.node->setTintColor(color);
}
}
void CompositeAnimation::setFlipX(bool flip) {
for (auto &entry : layers_) {
if (entry.node)
entry.node->setFlipX(flip);
}
}
void CompositeAnimation::setFlipY(bool flip) {
for (auto &entry : layers_) {
if (entry.node)
entry.node->setFlipY(flip);
}
}
} // namespace extra2d

View File

@ -1,220 +0,0 @@
#include <animation/frame_property.h>
namespace extra2d {
// ============================================================================
// FramePropertySet 实现
// ============================================================================
void FramePropertySet::set(FramePropertyKey key, FramePropertyValue value) {
properties_[key] = value;
}
void FramePropertySet::set(FramePropertyKey key, const std::string& value) {
FramePropertyValue pv;
pv.type = PropertyValueType::String;
pv.data.stringIndex = allocateString(value);
properties_[key] = pv;
}
void FramePropertySet::set(FramePropertyKey key, const std::vector<int>& value) {
FramePropertyValue pv;
pv.type = PropertyValueType::IntVector;
pv.data.vectorIndex = allocateVector(value);
properties_[key] = pv;
}
void FramePropertySet::setCustom(const std::string &key, std::any value) {
customProperties_[key] = std::move(value);
}
bool FramePropertySet::has(FramePropertyKey key) const {
return properties_.find(key) != properties_.end();
}
bool FramePropertySet::hasCustom(const std::string &key) const {
return customProperties_.find(key) != customProperties_.end();
}
void FramePropertySet::remove(FramePropertyKey key) {
properties_.erase(key);
}
void FramePropertySet::removeCustom(const std::string &key) {
customProperties_.erase(key);
}
void FramePropertySet::clear() {
properties_.clear();
customProperties_.clear();
stringPool_.clear();
vectorPool_.clear();
nextStringIndex_ = 0;
nextVectorIndex_ = 0;
}
std::optional<std::any> FramePropertySet::getCustom(const std::string &key) const {
auto it = customProperties_.find(key);
if (it == customProperties_.end())
return std::nullopt;
return it->second;
}
// ============================================================================
// 字符串池和vector池管理
// ============================================================================
uint32_t FramePropertySet::allocateString(const std::string& str) {
// 查找是否已存在相同字符串
for (uint32_t i = 0; i < stringPool_.size(); ++i) {
if (stringPool_[i] == str) {
return i;
}
}
// 分配新字符串
uint32_t index = static_cast<uint32_t>(stringPool_.size());
stringPool_.push_back(str);
return index;
}
uint32_t FramePropertySet::allocateVector(const std::vector<int>& vec) {
// 查找是否已存在相同vector
for (uint32_t i = 0; i < vectorPool_.size(); ++i) {
if (vectorPool_[i] == vec) {
return i;
}
}
// 分配新vector
uint32_t index = static_cast<uint32_t>(vectorPool_.size());
vectorPool_.push_back(vec);
return index;
}
const std::string* FramePropertySet::getString(uint32_t index) const {
if (index < stringPool_.size()) {
return &stringPool_[index];
}
return nullptr;
}
const std::vector<int>* FramePropertySet::getVector(uint32_t index) const {
if (index < vectorPool_.size()) {
return &vectorPool_[index];
}
return nullptr;
}
// ============================================================================
// 模板特化实现
// ============================================================================
template <> std::optional<bool> FramePropertySet::get<bool>(FramePropertyKey key) const {
auto it = properties_.find(key);
if (it == properties_.end()) return std::nullopt;
if (it->second.type == PropertyValueType::Bool) {
return it->second.data.boolValue;
}
return std::nullopt;
}
template <> std::optional<int> FramePropertySet::get<int>(FramePropertyKey key) const {
auto it = properties_.find(key);
if (it == properties_.end()) return std::nullopt;
if (it->second.type == PropertyValueType::Int) {
return it->second.data.intValue;
}
return std::nullopt;
}
template <> std::optional<float> FramePropertySet::get<float>(FramePropertyKey key) const {
auto it = properties_.find(key);
if (it == properties_.end()) return std::nullopt;
if (it->second.type == PropertyValueType::Float) {
return it->second.data.floatValue;
}
return std::nullopt;
}
template <> std::optional<Vec2> FramePropertySet::get<Vec2>(FramePropertyKey key) const {
auto it = properties_.find(key);
if (it == properties_.end()) return std::nullopt;
if (it->second.type == PropertyValueType::Vec2) {
return it->second.data.vec2Value;
}
return std::nullopt;
}
template <> std::optional<Color> FramePropertySet::get<Color>(FramePropertyKey key) const {
auto it = properties_.find(key);
if (it == properties_.end()) return std::nullopt;
if (it->second.type == PropertyValueType::Color) {
return it->second.data.colorValue;
}
return std::nullopt;
}
template <> std::optional<std::string> FramePropertySet::get<std::string>(FramePropertyKey key) const {
auto it = properties_.find(key);
if (it == properties_.end()) return std::nullopt;
if (it->second.type == PropertyValueType::String) {
const std::string* str = getString(it->second.data.stringIndex);
if (str) return *str;
}
return std::nullopt;
}
template <> std::optional<std::vector<int>> FramePropertySet::get<std::vector<int>>(FramePropertyKey key) const {
auto it = properties_.find(key);
if (it == properties_.end()) return std::nullopt;
if (it->second.type == PropertyValueType::IntVector) {
const std::vector<int>* vec = getVector(it->second.data.vectorIndex);
if (vec) return *vec;
}
return std::nullopt;
}
// ============================================================================
// 链式 API 实现
// ============================================================================
FramePropertySet &FramePropertySet::withSetFlag(int index) {
set(FramePropertyKey::SetFlag, FramePropertyValue(index));
return *this;
}
FramePropertySet &FramePropertySet::withPlaySound(const std::string &path) {
set(FramePropertyKey::PlaySound, path);
return *this;
}
FramePropertySet &FramePropertySet::withImageRate(const Vec2 &scale) {
set(FramePropertyKey::ImageRate, FramePropertyValue(scale));
return *this;
}
FramePropertySet &FramePropertySet::withImageRotate(float degrees) {
set(FramePropertyKey::ImageRotate, FramePropertyValue(degrees));
return *this;
}
FramePropertySet &FramePropertySet::withColorTint(const Color &color) {
set(FramePropertyKey::ColorTint, FramePropertyValue(color));
return *this;
}
FramePropertySet &FramePropertySet::withInterpolation(bool enabled) {
set(FramePropertyKey::Interpolation, FramePropertyValue(enabled));
return *this;
}
FramePropertySet &FramePropertySet::withBlendLinearDodge(bool enabled) {
set(FramePropertyKey::BlendLinearDodge, FramePropertyValue(enabled));
return *this;
}
FramePropertySet &FramePropertySet::withLoop(bool enabled) {
set(FramePropertyKey::Loop, FramePropertyValue(enabled));
return *this;
}
} // namespace extra2d

View File

@ -1,168 +0,0 @@
#include <animation/frame_renderer.h>
#include <graphics/texture.h>
namespace extra2d {
// ============================================================================
// 预加载
// ============================================================================
bool FrameRenderer::preloadFrames(const std::vector<AnimationFrame> &frames) {
releaseFrames();
spriteFrames_.reserve(frames.size());
maxFrameSize_ = Size{0.0f, 0.0f};
for (const auto &frame : frames) {
Ptr<SpriteFrame> sf = frame.spriteFrame;
// 如果帧自身没有 SpriteFrame尝试通过缓存获取
if (!sf && !frame.texturePath.empty()) {
sf = SpriteFrameCache::getInstance().getOrCreateFromFile(
frame.texturePath, frame.textureIndex);
}
spriteFrames_.push_back(sf);
// 更新最大帧尺寸
if (sf && sf->isValid()) {
const auto &rect = sf->getRect();
if (rect.size.width > maxFrameSize_.width)
maxFrameSize_.width = rect.size.width;
if (rect.size.height > maxFrameSize_.height)
maxFrameSize_.height = rect.size.height;
}
}
return true;
}
void FrameRenderer::releaseFrames() {
spriteFrames_.clear();
maxFrameSize_ = Size{0.0f, 0.0f};
}
// ============================================================================
// 渲染当前帧
// ============================================================================
void FrameRenderer::renderFrame(RenderBackend &renderer,
const AnimationFrame &frame, size_t frameIndex,
const Vec2 &position, float nodeOpacity,
const Color &tintColor, bool flipX,
bool flipY) {
if (frameIndex >= spriteFrames_.size())
return;
auto sf = spriteFrames_[frameIndex];
if (!sf || !sf->isValid())
return;
BlendMode blend = mapBlendMode(frame.properties);
Vec2 scale = frame.getEffectiveScale();
float rotation = frame.getEffectiveRotation();
Color frameColor = frame.getEffectiveColor();
// 合并帧颜色和节点染色
Color finalTint{tintColor.r * frameColor.r, tintColor.g * frameColor.g,
tintColor.b * frameColor.b,
tintColor.a * frameColor.a * nodeOpacity};
drawSpriteFrame(renderer, sf, position, frame.offset, scale, rotation, 1.0f,
finalTint, flipX, flipY, blend);
}
// ============================================================================
// 渲染插值帧
// ============================================================================
void FrameRenderer::renderInterpolated(
RenderBackend &renderer, const AnimationFrame &fromFrame, size_t fromIndex,
const InterpolatedProperties &props, const Vec2 &position,
float nodeOpacity, const Color &tintColor, bool flipX, bool flipY) {
if (fromIndex >= spriteFrames_.size())
return;
auto sf = spriteFrames_[fromIndex];
if (!sf || !sf->isValid())
return;
BlendMode blend = mapBlendMode(fromFrame.properties);
Color finalTint{tintColor.r * props.color.r, tintColor.g * props.color.g,
tintColor.b * props.color.b,
tintColor.a * props.color.a * nodeOpacity};
drawSpriteFrame(renderer, sf, position, props.position, props.scale,
props.rotation, 1.0f, finalTint, flipX, flipY, blend);
}
// ============================================================================
// 混合模式映射
// ============================================================================
BlendMode FrameRenderer::mapBlendMode(const FramePropertySet &props) {
if (props.has(FramePropertyKey::BlendAdditive)) {
auto val = props.get<bool>(FramePropertyKey::BlendAdditive);
if (val.has_value() && val.value())
return BlendMode::Additive;
}
if (props.has(FramePropertyKey::BlendLinearDodge)) {
auto val = props.get<bool>(FramePropertyKey::BlendLinearDodge);
if (val.has_value() && val.value())
return BlendMode::Additive; // 线性减淡 ≈ 加法混合
}
return BlendMode::Alpha;
}
// ============================================================================
// 查询
// ============================================================================
Ptr<SpriteFrame> FrameRenderer::getSpriteFrame(size_t frameIndex) const {
if (frameIndex >= spriteFrames_.size())
return nullptr;
return spriteFrames_[frameIndex];
}
// ============================================================================
// 内部绘制
// ============================================================================
void FrameRenderer::drawSpriteFrame(RenderBackend &renderer,
Ptr<SpriteFrame> sf, const Vec2 &position,
const Vec2 &offset, const Vec2 &scale,
float rotation, float opacity,
const Color &tint, bool flipX, bool flipY,
BlendMode blend) {
if (!sf || !sf->isValid())
return;
auto texture = sf->getTexture();
if (!texture)
return;
renderer.setBlendMode(blend);
const Rect &srcRect = sf->getRect();
// 计算目标矩形
float w = srcRect.size.width * std::abs(scale.x);
float h = srcRect.size.height * std::abs(scale.y);
Vec2 finalPos{position.x + offset.x, position.y + offset.y};
// 处理翻转(通过缩放符号)
float flipScaleX = flipX ? -1.0f : 1.0f;
float flipScaleY = flipY ? -1.0f : 1.0f;
// 锚点由 RenderBackend 在绘制时处理,这里只传递位置和尺寸
// 使用中心锚点(0.5, 0.5),所以 destRect 从 finalPos 开始,不预偏移
Rect destRect{{finalPos.x, finalPos.y}, {w * flipScaleX, h * flipScaleY}};
Color finalColor{tint.r, tint.g, tint.b, tint.a * opacity};
renderer.drawSprite(*texture, destRect, srcRect, finalColor, rotation,
Vec2{0.5f, 0.5f});
}
} // namespace extra2d

View File

@ -1,8 +0,0 @@
#include <animation/interpolation_engine.h>
namespace extra2d {
// InterpolationEngine 的实现全部在头文件中以静态内联方式完成
// 此文件保留用于未来可能需要的非内联实现
} // namespace extra2d

451
src/animation/tween.cpp Normal file
View File

@ -0,0 +1,451 @@
#include <animation/tween.h>
#include <scene/node.h>
#include <algorithm>
#include <cmath>
namespace extra2d {
class IntervalAction : public TweenAction {
public:
IntervalAction(Node *target, float duration, const TweenProperty &props,
const TweenOptions &options, bool isRelative)
: TweenAction(Type::Interval), target_(target), duration_(duration),
props_(props), options_(options), isRelative_(isRelative) {}
void update(float dt) override {
if (finished_)
return;
elapsed_ += dt;
float t = std::min(elapsed_ / duration_, 1.0f);
float easedT = EasingFunctions::apply(t, options_.easing);
applyProperties(easedT);
if (options_.onUpdate) {
options_.onUpdate(t);
}
if (t >= 1.0f) {
finished_ = true;
if (options_.onComplete) {
options_.onComplete();
}
}
}
bool isFinished() const override { return finished_; }
float getDuration() const override { return duration_; }
float getElapsed() const override { return elapsed_; }
void reset() override {
elapsed_ = 0.0f;
finished_ = false;
started_ = false;
}
void captureStartValues() {
if (started_)
return;
started_ = true;
if (target_) {
startPos_ = target_->getPosition();
startScale_ = target_->getScale();
startRotation_ = target_->getRotation();
startOpacity_ = target_->getOpacity();
}
}
private:
Node *target_;
float duration_;
TweenProperty props_;
TweenOptions options_;
bool isRelative_;
bool started_ = false;
float elapsed_ = 0.0f;
bool finished_ = false;
Vec2 startPos_;
Vec2 startScale_;
float startRotation_ = 0.0f;
float startOpacity_ = 1.0f;
void applyProperties(float t) {
if (!target_)
return;
if (props_.position.has_value()) {
Vec2 targetPos = isRelative_ ? startPos_ + props_.position.value()
: props_.position.value();
Vec2 newPos = Vec2::lerp(startPos_, targetPos, t);
target_->setPosition(newPos);
}
if (props_.scale.has_value()) {
Vec2 targetScale =
isRelative_ ? startScale_ + props_.scale.value()
: props_.scale.value();
Vec2 newScale = Vec2::lerp(startScale_, targetScale, t);
target_->setScale(newScale);
}
if (props_.rotation.has_value()) {
float targetRot = isRelative_ ? startRotation_ + props_.rotation.value()
: props_.rotation.value();
float newRot = startRotation_ + (targetRot - startRotation_) * t;
target_->setRotation(newRot);
}
if (props_.opacity.has_value()) {
float targetOpa = isRelative_ ? startOpacity_ + props_.opacity.value()
: props_.opacity.value();
float newOpa = startOpacity_ + (targetOpa - startOpacity_) * t;
target_->setOpacity(newOpa);
}
}
};
class DelayAction : public TweenAction {
public:
DelayAction(float duration)
: TweenAction(Type::Delay), duration_(duration) {}
void update(float dt) override {
elapsed_ += dt;
finished_ = elapsed_ >= duration_;
}
bool isFinished() const override { return finished_; }
float getDuration() const override { return duration_; }
float getElapsed() const override { return elapsed_; }
void reset() override {
elapsed_ = 0.0f;
finished_ = false;
}
private:
float duration_;
float elapsed_ = 0.0f;
bool finished_ = false;
};
class CallAction : public TweenAction {
public:
CallAction(std::function<void()> callback)
: TweenAction(Type::Call), callback_(std::move(callback)) {}
void update(float) override {
if (!called_) {
if (callback_) {
callback_();
}
called_ = true;
}
finished_ = true;
}
bool isFinished() const override { return finished_; }
float getDuration() const override { return 0.0f; }
float getElapsed() const override { return 0.0f; }
void reset() override {
called_ = false;
finished_ = false;
}
private:
std::function<void()> callback_;
bool called_ = false;
bool finished_ = false;
};
class SetAction : public TweenAction {
public:
SetAction(Node *target, const TweenProperty &props)
: TweenAction(Type::Call), target_(target), props_(props) {}
void update(float) override {
if (!applied_ && target_) {
if (props_.position.has_value())
target_->setPosition(props_.position.value());
if (props_.scale.has_value())
target_->setScale(props_.scale.value());
if (props_.rotation.has_value())
target_->setRotation(props_.rotation.value());
if (props_.opacity.has_value())
target_->setOpacity(props_.opacity.value());
applied_ = true;
}
finished_ = true;
}
bool isFinished() const override { return finished_; }
float getDuration() const override { return 0.0f; }
float getElapsed() const override { return 0.0f; }
void reset() override {
applied_ = false;
finished_ = false;
}
private:
Node *target_;
TweenProperty props_;
bool applied_ = false;
bool finished_ = false;
};
class ParallelAction : public TweenAction {
public:
ParallelAction() : TweenAction(Type::Parallel) {}
void addAction(std::unique_ptr<TweenAction> action) {
actions_.push_back(std::move(action));
}
void update(float dt) override {
if (finished_)
return;
bool allFinished = true;
for (auto &action : actions_) {
if (!action->isFinished()) {
action->update(dt);
allFinished = false;
}
}
finished_ = allFinished;
}
bool isFinished() const override { return finished_; }
float getDuration() const override {
float maxDur = 0.0f;
for (const auto &action : actions_) {
maxDur = std::max(maxDur, action->getDuration());
}
return maxDur;
}
float getElapsed() const override {
float maxElapsed = 0.0f;
for (const auto &action : actions_) {
maxElapsed = std::max(maxElapsed, action->getElapsed());
}
return maxElapsed;
}
void reset() override {
for (auto &action : actions_) {
action->reset();
}
finished_ = false;
}
private:
std::vector<std::unique_ptr<TweenAction>> actions_;
bool finished_ = false;
};
Tween::Tween(Node *target) : target_(target) {}
Tween::Tween(Node *target, const std::string &name)
: target_(target), name_(name) {}
Tween::~Tween() = default;
Tween Tween::create(Node *target) { return Tween(target); }
Tween Tween::create(Node *target, const std::string &name) {
return Tween(target, name);
}
Tween &Tween::to(float duration, const TweenProperty &props,
const TweenOptions &options) {
auto action =
std::make_unique<IntervalAction>(target_, duration, props, options, false);
addAction(std::move(action));
return *this;
}
Tween &Tween::by(float duration, const TweenProperty &props,
const TweenOptions &options) {
auto action =
std::make_unique<IntervalAction>(target_, duration, props, options, true);
addAction(std::move(action));
return *this;
}
Tween &Tween::set(const TweenProperty &props) {
auto action = std::make_unique<SetAction>(target_, props);
addAction(std::move(action));
return *this;
}
Tween &Tween::delay(float seconds) {
auto action = std::make_unique<DelayAction>(seconds);
addAction(std::move(action));
return *this;
}
Tween &Tween::call(Callback callback) {
auto action = std::make_unique<CallAction>(std::move(callback));
addAction(std::move(action));
return *this;
}
Tween &Tween::parallel() {
inParallel_ = true;
parallelActions_.clear();
return *this;
}
Tween &Tween::endParallel() {
if (inParallel_ && !parallelActions_.empty()) {
auto parallel = std::make_unique<ParallelAction>();
for (auto &action : parallelActions_) {
parallel->addAction(std::move(action));
}
actions_.push_back(std::move(parallel));
parallelActions_.clear();
}
inParallel_ = false;
return *this;
}
Tween &Tween::union_() { return *this; }
Tween &Tween::repeat(int times) {
repeatCount_ = times;
return *this;
}
Tween &Tween::repeatForever() {
repeatForever_ = true;
return *this;
}
Tween &Tween::yoyo(bool enabled) {
yoyo_ = enabled;
return *this;
}
void Tween::start() {
if (actions_.empty())
return;
playing_ = true;
paused_ = false;
finished_ = false;
currentActionIndex_ = 0;
currentRepeat_ = 0;
for (auto &action : actions_) {
action->reset();
}
if (auto *interval =
dynamic_cast<IntervalAction *>(actions_[0].get())) {
interval->captureStartValues();
}
}
void Tween::stop() {
playing_ = false;
finished_ = true;
}
void Tween::pause() { paused_ = true; }
void Tween::resume() { paused_ = false; }
void Tween::update(float dt) {
if (!playing_ || paused_ || finished_)
return;
if (currentActionIndex_ >= actions_.size()) {
if (repeatForever_ || currentRepeat_ < repeatCount_ - 1) {
currentRepeat_++;
resetAllActions();
currentActionIndex_ = 0;
if (yoyo_) {
reverse_ = !reverse_;
}
} else {
finished_ = true;
playing_ = false;
return;
}
}
auto &action = actions_[currentActionIndex_];
action->update(dt);
if (action->isFinished()) {
currentActionIndex_++;
if (currentActionIndex_ < actions_.size()) {
actions_[currentActionIndex_]->reset();
if (auto *interval = dynamic_cast<IntervalAction *>(
actions_[currentActionIndex_].get())) {
interval->captureStartValues();
}
}
}
}
bool Tween::isFinished() const { return finished_; }
bool Tween::isPlaying() const { return playing_; }
bool Tween::isPaused() const { return paused_; }
float Tween::getTotalDuration() const {
float total = 0.0f;
for (const auto &action : actions_) {
total += action->getDuration();
}
return total;
}
void Tween::addAction(std::unique_ptr<TweenAction> action) {
if (inParallel_) {
parallelActions_.push_back(std::move(action));
} else {
actions_.push_back(std::move(action));
}
}
void Tween::advanceToNextAction() {
currentActionIndex_++;
if (currentActionIndex_ < actions_.size()) {
actions_[currentActionIndex_]->reset();
}
}
void Tween::resetAllActions() {
for (auto &action : actions_) {
action->reset();
}
}
namespace tween {
TweenProperty combine(std::initializer_list<TweenProperty> props) {
TweenProperty result;
for (const auto &p : props) {
if (p.position.has_value())
result.position = p.position;
if (p.scale.has_value())
result.scale = p.scale;
if (p.rotation.has_value())
result.rotation = p.rotation;
if (p.opacity.has_value())
result.opacity = p.opacity;
if (p.color.has_value())
result.color = p.color;
}
return result;
}
} // namespace tween
} // namespace extra2d

View File

@ -0,0 +1,236 @@
#include <animation/tween_easing.h>
#include <cmath>
namespace extra2d {
float EasingFunctions::apply(float t, TweenEasing easing) {
return get(easing)(t);
}
EasingFunctions::EasingFunc EasingFunctions::get(TweenEasing easing) {
switch (easing) {
case TweenEasing::Linear: return linear;
case TweenEasing::QuadIn: return quadIn;
case TweenEasing::QuadOut: return quadOut;
case TweenEasing::QuadInOut: return quadInOut;
case TweenEasing::CubicIn: return cubicIn;
case TweenEasing::CubicOut: return cubicOut;
case TweenEasing::CubicInOut: return cubicInOut;
case TweenEasing::QuartIn: return quartIn;
case TweenEasing::QuartOut: return quartOut;
case TweenEasing::QuartInOut: return quartInOut;
case TweenEasing::QuintIn: return quintIn;
case TweenEasing::QuintOut: return quintOut;
case TweenEasing::QuintInOut: return quintInOut;
case TweenEasing::SineIn: return sineIn;
case TweenEasing::SineOut: return sineOut;
case TweenEasing::SineInOut: return sineInOut;
case TweenEasing::ExpoIn: return expoIn;
case TweenEasing::ExpoOut: return expoOut;
case TweenEasing::ExpoInOut: return expoInOut;
case TweenEasing::CircIn: return circIn;
case TweenEasing::CircOut: return circOut;
case TweenEasing::CircInOut: return circInOut;
case TweenEasing::ElasticIn: return elasticIn;
case TweenEasing::ElasticOut: return elasticOut;
case TweenEasing::ElasticInOut: return elasticInOut;
case TweenEasing::BackIn: return backIn;
case TweenEasing::BackOut: return backOut;
case TweenEasing::BackInOut: return backInOut;
case TweenEasing::BounceIn: return bounceIn;
case TweenEasing::BounceOut: return bounceOut;
case TweenEasing::BounceInOut: return bounceInOut;
case TweenEasing::Smooth: return smooth;
case TweenEasing::Fade: return fade;
default: return linear;
}
}
float EasingFunctions::linear(float t) {
return t;
}
float EasingFunctions::quadIn(float t) {
return t * t;
}
float EasingFunctions::quadOut(float t) {
return t * (2.0f - t);
}
float EasingFunctions::quadInOut(float t) {
if (t < 0.5f) {
return 2.0f * t * t;
}
return -1.0f + (4.0f - 2.0f * t) * t;
}
float EasingFunctions::cubicIn(float t) {
return t * t * t;
}
float EasingFunctions::cubicOut(float t) {
float t1 = t - 1.0f;
return t1 * t1 * t1 + 1.0f;
}
float EasingFunctions::cubicInOut(float t) {
if (t < 0.5f) {
return 4.0f * t * t * t;
}
float t1 = 2.0f * t - 2.0f;
return 0.5f * t1 * t1 * t1 + 1.0f;
}
float EasingFunctions::quartIn(float t) {
return t * t * t * t;
}
float EasingFunctions::quartOut(float t) {
float t1 = t - 1.0f;
return 1.0f - t1 * t1 * t1 * t1;
}
float EasingFunctions::quartInOut(float t) {
if (t < 0.5f) {
return 8.0f * t * t * t * t;
}
float t1 = t - 1.0f;
return 1.0f - 8.0f * t1 * t1 * t1 * t1;
}
float EasingFunctions::quintIn(float t) {
return t * t * t * t * t;
}
float EasingFunctions::quintOut(float t) {
float t1 = t - 1.0f;
return t1 * t1 * t1 * t1 * t1 + 1.0f;
}
float EasingFunctions::quintInOut(float t) {
if (t < 0.5f) {
return 16.0f * t * t * t * t * t;
}
float t1 = 2.0f * t - 2.0f;
return 0.5f * t1 * t1 * t1 * t1 * t1 + 1.0f;
}
float EasingFunctions::sineIn(float t) {
return 1.0f - std::cos(t * HALF_PI);
}
float EasingFunctions::sineOut(float t) {
return std::sin(t * HALF_PI);
}
float EasingFunctions::sineInOut(float t) {
return -0.5f * (std::cos(PI * t) - 1.0f);
}
float EasingFunctions::expoIn(float t) {
return t == 0.0f ? 0.0f : std::pow(2.0f, 10.0f * (t - 1.0f));
}
float EasingFunctions::expoOut(float t) {
return t == 1.0f ? 1.0f : 1.0f - std::pow(2.0f, -10.0f * t);
}
float EasingFunctions::expoInOut(float t) {
if (t == 0.0f) return 0.0f;
if (t == 1.0f) return 1.0f;
if (t < 0.5f) {
return 0.5f * std::pow(2.0f, 20.0f * t - 10.0f);
}
return 1.0f - 0.5f * std::pow(2.0f, -20.0f * t + 10.0f);
}
float EasingFunctions::circIn(float t) {
return 1.0f - std::sqrt(1.0f - t * t);
}
float EasingFunctions::circOut(float t) {
return std::sqrt(2.0f * t - t * t);
}
float EasingFunctions::circInOut(float t) {
if (t < 0.5f) {
return 0.5f * (1.0f - std::sqrt(1.0f - 4.0f * t * t));
}
return 0.5f * (std::sqrt(4.0f * t - 4.0f * t * t - 3.0f) + 1.0f);
}
float EasingFunctions::elasticIn(float t) {
if (t == 0.0f) return 0.0f;
if (t == 1.0f) return 1.0f;
return -std::pow(2.0f, 10.0f * (t - 1.0f)) * std::sin((t - 1.1f) * ELASTIC_CONST);
}
float EasingFunctions::elasticOut(float t) {
if (t == 0.0f) return 0.0f;
if (t == 1.0f) return 1.0f;
return std::pow(2.0f, -10.0f * t) * std::sin((t - 0.1f) * ELASTIC_CONST) + 1.0f;
}
float EasingFunctions::elasticInOut(float t) {
if (t == 0.0f) return 0.0f;
if (t == 1.0f) return 1.0f;
if (t < 0.5f) {
return -0.5f * std::pow(2.0f, 20.0f * t - 10.0f) * std::sin((20.0f * t - 11.125f) * PI / 4.5f);
}
return std::pow(2.0f, -20.0f * t + 10.0f) * std::sin((20.0f * t - 11.125f) * PI / 4.5f) * 0.5f + 1.0f;
}
float EasingFunctions::backIn(float t) {
return t * t * ((BACK_CONST + 1.0f) * t - BACK_CONST);
}
float EasingFunctions::backOut(float t) {
float t1 = t - 1.0f;
return t1 * t1 * ((BACK_CONST + 1.0f) * t1 + BACK_CONST) + 1.0f;
}
float EasingFunctions::backInOut(float t) {
float s = BACK_CONST * 1.525f;
if (t < 0.5f) {
return 0.5f * (t * 2.0f) * (t * 2.0f) * ((s + 1.0f) * 2.0f * t - s);
}
float t1 = 2.0f * t - 2.0f;
return 0.5f * (t1 * t1 * ((s + 1.0f) * t1 + s) + 2.0f);
}
float EasingFunctions::bounceIn(float t) {
return 1.0f - bounceOut(1.0f - t);
}
float EasingFunctions::bounceOut(float t) {
if (t < 1.0f / 2.75f) {
return 7.5625f * t * t;
} else if (t < 2.0f / 2.75f) {
float t1 = t - 1.5f / 2.75f;
return 7.5625f * t1 * t1 + 0.75f;
} else if (t < 2.5f / 2.75f) {
float t1 = t - 2.25f / 2.75f;
return 7.5625f * t1 * t1 + 0.9375f;
} else {
float t1 = t - 2.625f / 2.75f;
return 7.5625f * t1 * t1 + 0.984375f;
}
}
float EasingFunctions::bounceInOut(float t) {
if (t < 0.5f) {
return 0.5f * bounceIn(t * 2.0f);
}
return 0.5f * bounceOut(t * 2.0f - 1.0f) + 0.5f;
}
float EasingFunctions::smooth(float t) {
return t * t * (3.0f - 2.0f * t);
}
float EasingFunctions::fade(float t) {
return t * t * t * (t * (6.0f * t - 15.0f) + 10.0f);
}
} // namespace extra2d

View File

@ -5,13 +5,11 @@
namespace extra2d { namespace extra2d {
Input::Input() Input::Input()
: controller_(nullptr), : controller_(nullptr), leftStickX_(0.0f), leftStickY_(0.0f),
leftStickX_(0.0f), leftStickY_(0.0f), rightStickX_(0.0f), rightStickY_(0.0f), mouseScroll_(0.0f),
rightStickX_(0.0f), rightStickY_(0.0f), prevMouseScroll_(0.0f), touching_(false), prevTouching_(false),
mouseScroll_(0.0f), prevMouseScroll_(0.0f), touchCount_(0) {
touching_(false), prevTouching_(false), touchCount_(0) {
// 初始化所有状态数组
keysDown_.fill(false); keysDown_.fill(false);
prevKeysDown_.fill(false); prevKeysDown_.fill(false);
buttonsDown_.fill(false); buttonsDown_.fill(false);
@ -23,7 +21,6 @@ Input::Input()
Input::~Input() { shutdown(); } Input::~Input() { shutdown(); }
void Input::init() { void Input::init() {
// 打开第一个可用的游戏控制器
for (int i = 0; i < SDL_NumJoysticks(); ++i) { for (int i = 0; i < SDL_NumJoysticks(); ++i) {
if (SDL_IsGameController(i)) { if (SDL_IsGameController(i)) {
controller_ = SDL_GameControllerOpen(i); controller_ = SDL_GameControllerOpen(i);
@ -39,13 +36,10 @@ void Input::init() {
E2D_LOG_WARN("No game controller found"); E2D_LOG_WARN("No game controller found");
} }
// PC 端获取初始鼠标状态
#ifndef PLATFORM_SWITCH
int mouseX, mouseY; int mouseX, mouseY;
SDL_GetMouseState(&mouseX, &mouseY); SDL_GetMouseState(&mouseX, &mouseY);
mousePosition_ = Vec2(static_cast<float>(mouseX), static_cast<float>(mouseY)); mousePosition_ = Vec2(static_cast<float>(mouseX), static_cast<float>(mouseY));
prevMousePosition_ = mousePosition_; prevMousePosition_ = mousePosition_;
#endif
} }
void Input::shutdown() { void Input::shutdown() {
@ -56,7 +50,6 @@ void Input::shutdown() {
} }
void Input::update() { void Input::update() {
// 保存上一帧状态
prevKeysDown_ = keysDown_; prevKeysDown_ = keysDown_;
prevButtonsDown_ = buttonsDown_; prevButtonsDown_ = buttonsDown_;
prevMouseButtonsDown_ = mouseButtonsDown_; prevMouseButtonsDown_ = mouseButtonsDown_;
@ -65,7 +58,6 @@ void Input::update() {
prevTouching_ = touching_; prevTouching_ = touching_;
prevTouchPosition_ = touchPosition_; prevTouchPosition_ = touchPosition_;
// 更新各输入设备状态
updateKeyboard(); updateKeyboard();
updateMouse(); updateMouse();
updateGamepad(); updateGamepad();
@ -73,50 +65,44 @@ void Input::update() {
} }
void Input::updateKeyboard() { void Input::updateKeyboard() {
// 获取当前键盘状态 const Uint8 *state = SDL_GetKeyboardState(nullptr);
const Uint8* state = SDL_GetKeyboardState(nullptr);
for (int i = 0; i < MAX_KEYS; ++i) { for (int i = 0; i < MAX_KEYS; ++i) {
keysDown_[i] = state[i] != 0; keysDown_[i] = state[i] != 0;
} }
} }
void Input::updateMouse() { void Input::updateMouse() {
#ifndef PLATFORM_SWITCH
// 更新鼠标位置
int mouseX, mouseY; int mouseX, mouseY;
Uint32 buttonState = SDL_GetMouseState(&mouseX, &mouseY); Uint32 buttonState = SDL_GetMouseState(&mouseX, &mouseY);
mousePosition_ = Vec2(static_cast<float>(mouseX), static_cast<float>(mouseY)); mousePosition_ = Vec2(static_cast<float>(mouseX), static_cast<float>(mouseY));
// 更新鼠标按钮状态
mouseButtonsDown_[0] = (buttonState & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0; mouseButtonsDown_[0] = (buttonState & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0;
mouseButtonsDown_[1] = (buttonState & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0; mouseButtonsDown_[1] = (buttonState & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0;
mouseButtonsDown_[2] = (buttonState & SDL_BUTTON(SDL_BUTTON_MIDDLE)) != 0; mouseButtonsDown_[2] = (buttonState & SDL_BUTTON(SDL_BUTTON_MIDDLE)) != 0;
mouseButtonsDown_[3] = (buttonState & SDL_BUTTON(SDL_BUTTON_X1)) != 0; mouseButtonsDown_[3] = (buttonState & SDL_BUTTON(SDL_BUTTON_X1)) != 0;
mouseButtonsDown_[4] = (buttonState & SDL_BUTTON(SDL_BUTTON_X2)) != 0; mouseButtonsDown_[4] = (buttonState & SDL_BUTTON(SDL_BUTTON_X2)) != 0;
// 处理鼠标滚轮事件(需要在事件循环中处理,这里简化处理)
// 实际滚轮值通过 SDL_MOUSEWHEEL 事件更新
#endif
} }
void Input::updateGamepad() { void Input::updateGamepad() {
if (controller_) { if (controller_) {
// 更新按钮状态
for (int i = 0; i < MAX_BUTTONS; ++i) { for (int i = 0; i < MAX_BUTTONS; ++i) {
buttonsDown_[i] = buttonsDown_[i] =
SDL_GameControllerGetButton( SDL_GameControllerGetButton(
controller_, static_cast<SDL_GameControllerButton>(i)) != 0; controller_, static_cast<SDL_GameControllerButton>(i)) != 0;
} }
// 读取摇杆(归一化到 -1.0 ~ 1.0
leftStickX_ = static_cast<float>(SDL_GameControllerGetAxis( leftStickX_ = static_cast<float>(SDL_GameControllerGetAxis(
controller_, SDL_CONTROLLER_AXIS_LEFTX)) / 32767.0f; controller_, SDL_CONTROLLER_AXIS_LEFTX)) /
32767.0f;
leftStickY_ = static_cast<float>(SDL_GameControllerGetAxis( leftStickY_ = static_cast<float>(SDL_GameControllerGetAxis(
controller_, SDL_CONTROLLER_AXIS_LEFTY)) / 32767.0f; controller_, SDL_CONTROLLER_AXIS_LEFTY)) /
32767.0f;
rightStickX_ = static_cast<float>(SDL_GameControllerGetAxis( rightStickX_ = static_cast<float>(SDL_GameControllerGetAxis(
controller_, SDL_CONTROLLER_AXIS_RIGHTX)) / 32767.0f; controller_, SDL_CONTROLLER_AXIS_RIGHTX)) /
32767.0f;
rightStickY_ = static_cast<float>(SDL_GameControllerGetAxis( rightStickY_ = static_cast<float>(SDL_GameControllerGetAxis(
controller_, SDL_CONTROLLER_AXIS_RIGHTY)) / 32767.0f; controller_, SDL_CONTROLLER_AXIS_RIGHTY)) /
32767.0f;
} else { } else {
buttonsDown_.fill(false); buttonsDown_.fill(false);
leftStickX_ = leftStickY_ = rightStickX_ = rightStickY_ = 0.0f; leftStickX_ = leftStickY_ = rightStickX_ = rightStickY_ = 0.0f;
@ -124,8 +110,6 @@ void Input::updateGamepad() {
} }
void Input::updateTouch() { void Input::updateTouch() {
#ifdef PLATFORM_SWITCH
// Switch 原生触摸屏支持
SDL_TouchID touchId = SDL_GetTouchDevice(0); SDL_TouchID touchId = SDL_GetTouchDevice(0);
if (touchId != 0) { if (touchId != 0) {
touchCount_ = SDL_GetNumTouchFingers(touchId); touchCount_ = SDL_GetNumTouchFingers(touchId);
@ -133,33 +117,12 @@ void Input::updateTouch() {
SDL_Finger *finger = SDL_GetTouchFinger(touchId, 0); SDL_Finger *finger = SDL_GetTouchFinger(touchId, 0);
if (finger) { if (finger) {
touching_ = true; touching_ = true;
// SDL 触摸坐标是归一化 0.0~1.0,转换为像素坐标
touchPosition_ = Vec2(finger->x * 1280.0f, finger->y * 720.0f);
} else {
touching_ = false;
}
} else {
touching_ = false;
}
} else {
touchCount_ = 0;
touching_ = false;
}
#else
// PC 端:触摸屏可选支持(如果有触摸设备)
SDL_TouchID touchId = SDL_GetTouchDevice(0);
if (touchId != 0) {
touchCount_ = SDL_GetNumTouchFingers(touchId);
if (touchCount_ > 0) {
SDL_Finger *finger = SDL_GetTouchFinger(touchId, 0);
if (finger) {
touching_ = true;
// PC 端需要根据窗口大小转换坐标
int windowWidth, windowHeight; int windowWidth, windowHeight;
SDL_Window* window = SDL_GL_GetCurrentWindow(); SDL_Window *window = SDL_GL_GetCurrentWindow();
if (window) { if (window) {
SDL_GetWindowSize(window, &windowWidth, &windowHeight); SDL_GetWindowSize(window, &windowWidth, &windowHeight);
touchPosition_ = Vec2(finger->x * windowWidth, finger->y * windowHeight); touchPosition_ =
Vec2(finger->x * windowWidth, finger->y * windowHeight);
} else { } else {
touchPosition_ = Vec2(finger->x * 1280.0f, finger->y * 720.0f); touchPosition_ = Vec2(finger->x * 1280.0f, finger->y * 720.0f);
} }
@ -173,113 +136,34 @@ void Input::updateTouch() {
touchCount_ = 0; touchCount_ = 0;
touching_ = false; touching_ = false;
} }
#endif
} }
// ============================================================================ // ============================================================================
// 键盘输入 // 键盘输入
// ============================================================================ // ============================================================================
SDL_GameControllerButton Input::mapKeyToButton(int keyCode) const {
switch (keyCode) {
// 方向键 → DPad
case Key::Up:
return SDL_CONTROLLER_BUTTON_DPAD_UP;
case Key::Down:
return SDL_CONTROLLER_BUTTON_DPAD_DOWN;
case Key::Left:
return SDL_CONTROLLER_BUTTON_DPAD_LEFT;
case Key::Right:
return SDL_CONTROLLER_BUTTON_DPAD_RIGHT;
// WASD → 也映射到 DPad
case Key::W:
return SDL_CONTROLLER_BUTTON_DPAD_UP;
case Key::S:
return SDL_CONTROLLER_BUTTON_DPAD_DOWN;
case Key::A:
return SDL_CONTROLLER_BUTTON_DPAD_LEFT;
case Key::D:
return SDL_CONTROLLER_BUTTON_DPAD_RIGHT;
// 常用键 → 手柄按钮
case Key::Z:
return SDL_CONTROLLER_BUTTON_B;
case Key::X:
return SDL_CONTROLLER_BUTTON_A;
case Key::C:
return SDL_CONTROLLER_BUTTON_Y;
case Key::V:
return SDL_CONTROLLER_BUTTON_X;
case Key::Space:
return SDL_CONTROLLER_BUTTON_A;
case Key::Enter:
return SDL_CONTROLLER_BUTTON_A;
case Key::Escape:
return SDL_CONTROLLER_BUTTON_START;
// 肩键
case Key::Q:
return SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
case Key::E:
return SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
// Start/Select
case Key::Tab:
return SDL_CONTROLLER_BUTTON_BACK;
case Key::Backspace:
return SDL_CONTROLLER_BUTTON_START;
default:
return SDL_CONTROLLER_BUTTON_INVALID;
}
}
bool Input::isKeyDown(int keyCode) const { bool Input::isKeyDown(int keyCode) const {
#ifdef PLATFORM_SWITCH
// Switch: 映射到手柄按钮
SDL_GameControllerButton button = mapKeyToButton(keyCode);
if (button == SDL_CONTROLLER_BUTTON_INVALID)
return false;
return buttonsDown_[button];
#else
// PC: 直接使用键盘扫描码
SDL_Scancode scancode = SDL_GetScancodeFromKey(keyCode); SDL_Scancode scancode = SDL_GetScancodeFromKey(keyCode);
if (scancode >= 0 && scancode < MAX_KEYS) { if (scancode >= 0 && scancode < MAX_KEYS) {
return keysDown_[scancode]; return keysDown_[scancode];
} }
return false; return false;
#endif
} }
bool Input::isKeyPressed(int keyCode) const { bool Input::isKeyPressed(int keyCode) const {
#ifdef PLATFORM_SWITCH
SDL_GameControllerButton button = mapKeyToButton(keyCode);
if (button == SDL_CONTROLLER_BUTTON_INVALID)
return false;
return buttonsDown_[button] && !prevButtonsDown_[button];
#else
SDL_Scancode scancode = SDL_GetScancodeFromKey(keyCode); SDL_Scancode scancode = SDL_GetScancodeFromKey(keyCode);
if (scancode >= 0 && scancode < MAX_KEYS) { if (scancode >= 0 && scancode < MAX_KEYS) {
return keysDown_[scancode] && !prevKeysDown_[scancode]; return keysDown_[scancode] && !prevKeysDown_[scancode];
} }
return false; return false;
#endif
} }
bool Input::isKeyReleased(int keyCode) const { bool Input::isKeyReleased(int keyCode) const {
#ifdef PLATFORM_SWITCH
SDL_GameControllerButton button = mapKeyToButton(keyCode);
if (button == SDL_CONTROLLER_BUTTON_INVALID)
return false;
return !buttonsDown_[button] && prevButtonsDown_[button];
#else
SDL_Scancode scancode = SDL_GetScancodeFromKey(keyCode); SDL_Scancode scancode = SDL_GetScancodeFromKey(keyCode);
if (scancode >= 0 && scancode < MAX_KEYS) { if (scancode >= 0 && scancode < MAX_KEYS) {
return !keysDown_[scancode] && prevKeysDown_[scancode]; return !keysDown_[scancode] && prevKeysDown_[scancode];
} }
return false; return false;
#endif
} }
// ============================================================================ // ============================================================================
@ -316,103 +200,40 @@ bool Input::isMouseDown(MouseButton button) const {
int index = static_cast<int>(button); int index = static_cast<int>(button);
if (index < 0 || index >= 8) if (index < 0 || index >= 8)
return false; return false;
#ifdef PLATFORM_SWITCH
// Switch: 左键映射到触摸,右键映射到 A 键
if (button == MouseButton::Left) {
return touching_;
}
if (button == MouseButton::Right) {
return buttonsDown_[SDL_CONTROLLER_BUTTON_A];
}
return false;
#else
// PC: 直接使用鼠标按钮
return mouseButtonsDown_[index]; return mouseButtonsDown_[index];
#endif
} }
bool Input::isMousePressed(MouseButton button) const { bool Input::isMousePressed(MouseButton button) const {
int index = static_cast<int>(button); int index = static_cast<int>(button);
if (index < 0 || index >= 8) if (index < 0 || index >= 8)
return false; return false;
#ifdef PLATFORM_SWITCH
if (button == MouseButton::Left) {
return touching_ && !prevTouching_;
}
if (button == MouseButton::Right) {
return buttonsDown_[SDL_CONTROLLER_BUTTON_A] &&
!prevButtonsDown_[SDL_CONTROLLER_BUTTON_A];
}
return false;
#else
return mouseButtonsDown_[index] && !prevMouseButtonsDown_[index]; return mouseButtonsDown_[index] && !prevMouseButtonsDown_[index];
#endif
} }
bool Input::isMouseReleased(MouseButton button) const { bool Input::isMouseReleased(MouseButton button) const {
int index = static_cast<int>(button); int index = static_cast<int>(button);
if (index < 0 || index >= 8) if (index < 0 || index >= 8)
return false; return false;
#ifdef PLATFORM_SWITCH
if (button == MouseButton::Left) {
return !touching_ && prevTouching_;
}
if (button == MouseButton::Right) {
return !buttonsDown_[SDL_CONTROLLER_BUTTON_A] &&
prevButtonsDown_[SDL_CONTROLLER_BUTTON_A];
}
return false;
#else
return !mouseButtonsDown_[index] && prevMouseButtonsDown_[index]; return !mouseButtonsDown_[index] && prevMouseButtonsDown_[index];
#endif
} }
Vec2 Input::getMousePosition() const { Vec2 Input::getMousePosition() const { return mousePosition_; }
#ifdef PLATFORM_SWITCH
return touchPosition_;
#else
return mousePosition_;
#endif
}
Vec2 Input::getMouseDelta() const { Vec2 Input::getMouseDelta() const {
#ifdef PLATFORM_SWITCH
if (touching_ && prevTouching_) {
return touchPosition_ - prevTouchPosition_;
}
return Vec2::Zero();
#else
return mousePosition_ - prevMousePosition_; return mousePosition_ - prevMousePosition_;
#endif
} }
void Input::setMousePosition(const Vec2 &position) { void Input::setMousePosition(const Vec2 &position) {
#ifndef PLATFORM_SWITCH SDL_WarpMouseInWindow(SDL_GL_GetCurrentWindow(), static_cast<int>(position.x),
SDL_WarpMouseInWindow(SDL_GL_GetCurrentWindow(),
static_cast<int>(position.x),
static_cast<int>(position.y)); static_cast<int>(position.y));
#else
(void)position;
#endif
} }
void Input::setMouseVisible(bool visible) { void Input::setMouseVisible(bool visible) {
#ifndef PLATFORM_SWITCH
SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE); SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE);
#else
(void)visible;
#endif
} }
void Input::setMouseLocked(bool locked) { void Input::setMouseLocked(bool locked) {
#ifndef PLATFORM_SWITCH
SDL_SetRelativeMouseMode(locked ? SDL_TRUE : SDL_FALSE); SDL_SetRelativeMouseMode(locked ? SDL_TRUE : SDL_FALSE);
#else
(void)locked;
#endif
} }
// ============================================================================ // ============================================================================
@ -420,30 +241,66 @@ void Input::setMouseLocked(bool locked) {
// ============================================================================ // ============================================================================
bool Input::isAnyKeyDown() const { bool Input::isAnyKeyDown() const {
#ifdef PLATFORM_SWITCH
for (int i = 0; i < MAX_BUTTONS; ++i) {
if (buttonsDown_[i])
return true;
}
#else
for (int i = 0; i < MAX_KEYS; ++i) { for (int i = 0; i < MAX_KEYS; ++i) {
if (keysDown_[i]) if (keysDown_[i])
return true; return true;
} }
#endif
return false; return false;
} }
bool Input::isAnyMouseDown() const { bool Input::isAnyMouseDown() const {
#ifdef PLATFORM_SWITCH
return touching_;
#else
for (int i = 0; i < 8; ++i) { for (int i = 0; i < 8; ++i) {
if (mouseButtonsDown_[i]) if (mouseButtonsDown_[i])
return true; return true;
} }
return false; return false;
#endif
} }
// ============================================================================
// 事件处理
// ============================================================================
void Input::onControllerAdded(int deviceIndex) {
if (SDL_IsGameController(deviceIndex)) {
SDL_GameController *controller = SDL_GameControllerOpen(deviceIndex);
if (controller) {
if (controller_) {
SDL_GameControllerClose(controller_);
}
controller_ = controller;
E2D_LOG_INFO("GameController connected: {}",
SDL_GameControllerName(controller_));
} else {
E2D_LOG_ERROR("Failed to open GameController: {}", SDL_GetError());
}
}
}
void Input::onControllerRemoved(int instanceId) {
if (controller_) {
SDL_Joystick *joystick = SDL_GameControllerGetJoystick(controller_);
if (joystick) {
SDL_JoystickID joyInstanceId = SDL_JoystickInstanceID(joystick);
if (joyInstanceId == instanceId) {
E2D_LOG_INFO("GameController disconnected");
SDL_GameControllerClose(controller_);
controller_ = nullptr;
for (int i = 0; i < SDL_NumJoysticks(); ++i) {
if (SDL_IsGameController(i)) {
controller_ = SDL_GameControllerOpen(i);
if (controller_) {
E2D_LOG_INFO("Switched to GameController: {}",
SDL_GameControllerName(controller_));
break;
}
}
}
}
}
}
}
void Input::onMouseWheel(float x, float y) { mouseScroll_ += y; }
} // namespace extra2d } // namespace extra2d

View File

@ -12,12 +12,13 @@ namespace extra2d {
Window::Window() Window::Window()
: sdlWindow_(nullptr), glContext_(nullptr), currentCursor_(nullptr), : sdlWindow_(nullptr), glContext_(nullptr), currentCursor_(nullptr),
width_(1280), height_(720), vsync_(true), shouldClose_(false), width_(1280), height_(720), vsync_(true), shouldClose_(false),
fullscreen_(true), focused_(true), contentScaleX_(1.0f), contentScaleY_(1.0f), fullscreen_(true), focused_(true), contentScaleX_(1.0f),
enableDpiScale_(true), userData_(nullptr), eventQueue_(nullptr) { contentScaleY_(1.0f), enableDpiScale_(true), userData_(nullptr),
// 初始化光标数组 eventQueue_(nullptr) {
for (int i = 0; i < 9; ++i) { // 初始化光标数组
sdlCursors_[i] = nullptr; for (int i = 0; i < 9; ++i) {
} sdlCursors_[i] = nullptr;
}
} }
Window::~Window() { destroy(); } Window::~Window() { destroy(); }
@ -55,7 +56,8 @@ bool Window::create(const WindowConfig &config) {
bool Window::initSDL(const WindowConfig &config) { bool Window::initSDL(const WindowConfig &config) {
// SDL2 全局初始化(视频 + 游戏控制器 + 音频) // SDL2 全局初始化(视频 + 游戏控制器 + 音频)
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER | SDL_INIT_AUDIO) != 0) { if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER | SDL_INIT_AUDIO) !=
0) {
E2D_LOG_ERROR("SDL_Init failed: {}", SDL_GetError()); E2D_LOG_ERROR("SDL_Init failed: {}", SDL_GetError());
return false; return false;
} }
@ -78,7 +80,7 @@ bool Window::initSDL(const WindowConfig &config) {
// 创建 SDL2 窗口 // 创建 SDL2 窗口
Uint32 windowFlags = SDL_WINDOW_OPENGL; Uint32 windowFlags = SDL_WINDOW_OPENGL;
// 根据配置设置窗口模式 // 根据配置设置窗口模式
if (config.fullscreen) { if (config.fullscreen) {
// Switch 平台使用 SDL_WINDOW_FULLSCREEN固定分辨率 // Switch 平台使用 SDL_WINDOW_FULLSCREEN固定分辨率
@ -101,7 +103,7 @@ bool Window::initSDL(const WindowConfig &config) {
config.centerWindow ? SDL_WINDOWPOS_CENTERED : SDL_WINDOWPOS_UNDEFINED, config.centerWindow ? SDL_WINDOWPOS_CENTERED : SDL_WINDOWPOS_UNDEFINED,
config.centerWindow ? SDL_WINDOWPOS_CENTERED : SDL_WINDOWPOS_UNDEFINED, config.centerWindow ? SDL_WINDOWPOS_CENTERED : SDL_WINDOWPOS_UNDEFINED,
width_, height_, windowFlags); width_, height_, windowFlags);
if (!sdlWindow_) { if (!sdlWindow_) {
E2D_LOG_ERROR("SDL_CreateWindow failed: {}", SDL_GetError()); E2D_LOG_ERROR("SDL_CreateWindow failed: {}", SDL_GetError());
SDL_Quit(); SDL_Quit();
@ -129,7 +131,8 @@ bool Window::initSDL(const WindowConfig &config) {
} }
// 加载 OpenGL ES 函数指针 // 加载 OpenGL ES 函数指针
if (gladLoadGLES2Loader(reinterpret_cast<GLADloadproc>(SDL_GL_GetProcAddress)) == 0) { if (gladLoadGLES2Loader(
reinterpret_cast<GLADloadproc>(SDL_GL_GetProcAddress)) == 0) {
E2D_LOG_ERROR("gladLoadGLES2Loader failed"); E2D_LOG_ERROR("gladLoadGLES2Loader failed");
SDL_GL_DeleteContext(glContext_); SDL_GL_DeleteContext(glContext_);
glContext_ = nullptr; glContext_ = nullptr;
@ -158,7 +161,7 @@ bool Window::initSDL(const WindowConfig &config) {
void Window::deinitSDL() { void Window::deinitSDL() {
deinitCursors(); deinitCursors();
if (glContext_) { if (glContext_) {
SDL_GL_DeleteContext(glContext_); SDL_GL_DeleteContext(glContext_);
glContext_ = nullptr; glContext_ = nullptr;
@ -217,6 +220,28 @@ void Window::pollEvents() {
break; break;
} }
break; break;
case SDL_CONTROLLERDEVICEADDED:
if (input_) {
input_->onControllerAdded(event.cdevice.which);
}
break;
case SDL_CONTROLLERDEVICEREMOVED:
if (input_) {
input_->onControllerRemoved(event.cdevice.which);
}
break;
case SDL_CONTROLLERDEVICEREMAPPED:
E2D_LOG_INFO("Controller device remapped");
break;
case SDL_MOUSEWHEEL:
if (input_) {
input_->onMouseWheel(event.wheel.x, event.wheel.y);
}
break;
} }
} }

View File

@ -1,5 +1,6 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <animation/tween.h>
#include <graphics/render_command.h> #include <graphics/render_command.h>
#include <scene/node.h> #include <scene/node.h>
#include <scene/scene.h> #include <scene/scene.h>
@ -334,6 +335,7 @@ void Node::onExit() {
} }
void Node::onUpdate(float dt) { void Node::onUpdate(float dt) {
updateTweens(dt);
onUpdateNode(dt); onUpdateNode(dt);
// Update children // Update children
@ -460,4 +462,142 @@ void Node::collectRenderCommands(std::vector<RenderCommand> &commands,
} }
} }
Tween& Node::tween() {
auto tw = std::make_shared<Tween>(this);
tweens_.push_back(tw);
return *tweens_.back();
}
Tween& Node::tween(const std::string &name) {
auto tw = std::make_shared<Tween>(this, name);
tweens_.push_back(tw);
return *tweens_.back();
}
Tween &Node::moveTo(const Vec2 &pos, float duration, TweenEasing easing) {
TweenProperty props;
props.position = pos;
TweenOptions options;
options.easing = easing;
auto tw = std::make_shared<Tween>(this);
tw->to(duration, props, options);
tw->start();
tweens_.push_back(tw);
return *tweens_.back();
}
Tween &Node::moveBy(const Vec2 &delta, float duration, TweenEasing easing) {
TweenProperty props;
props.position = delta;
TweenOptions options;
options.easing = easing;
auto tw = std::make_shared<Tween>(this);
tw->by(duration, props, options);
tw->start();
tweens_.push_back(tw);
return *tweens_.back();
}
Tween &Node::scaleTo(float scale, float duration, TweenEasing easing) {
TweenProperty props;
props.scale = Vec2(scale, scale);
TweenOptions options;
options.easing = easing;
auto tw = std::make_shared<Tween>(this);
tw->to(duration, props, options);
tw->start();
tweens_.push_back(tw);
return *tweens_.back();
}
Tween &Node::scaleBy(float delta, float duration, TweenEasing easing) {
TweenProperty props;
props.scale = Vec2(delta, delta);
TweenOptions options;
options.easing = easing;
auto tw = std::make_shared<Tween>(this);
tw->by(duration, props, options);
tw->start();
tweens_.push_back(tw);
return *tweens_.back();
}
Tween &Node::rotateTo(float degrees, float duration, TweenEasing easing) {
TweenProperty props;
props.rotation = degrees;
TweenOptions options;
options.easing = easing;
auto tw = std::make_shared<Tween>(this);
tw->to(duration, props, options);
tw->start();
tweens_.push_back(tw);
return *tweens_.back();
}
Tween &Node::rotateBy(float degrees, float duration, TweenEasing easing) {
TweenProperty props;
props.rotation = degrees;
TweenOptions options;
options.easing = easing;
auto tw = std::make_shared<Tween>(this);
tw->by(duration, props, options);
tw->start();
tweens_.push_back(tw);
return *tweens_.back();
}
Tween &Node::fadeIn(float duration, TweenEasing easing) {
TweenProperty props;
props.opacity = 1.0f;
TweenOptions options;
options.easing = easing;
auto tw = std::make_shared<Tween>(this);
tw->to(duration, props, options);
tw->start();
tweens_.push_back(tw);
return *tweens_.back();
}
Tween &Node::fadeOut(float duration, TweenEasing easing) {
TweenProperty props;
props.opacity = 0.0f;
TweenOptions options;
options.easing = easing;
auto tw = std::make_shared<Tween>(this);
tw->to(duration, props, options);
tw->start();
tweens_.push_back(tw);
return *tweens_.back();
}
Tween &Node::fadeTo(float opacity, float duration, TweenEasing easing) {
TweenProperty props;
props.opacity = opacity;
TweenOptions options;
options.easing = easing;
auto tw = std::make_shared<Tween>(this);
tw->to(duration, props, options);
tw->start();
tweens_.push_back(tw);
return *tweens_.back();
}
void Node::stopAllTweens() {
for (auto &tw : tweens_) {
tw->stop();
}
tweens_.clear();
}
void Node::updateTweens(float dt) {
for (auto it = tweens_.begin(); it != tweens_.end();) {
(*it)->update(dt);
if ((*it)->isFinished()) {
it = tweens_.erase(it);
} else {
++it;
}
}
}
} // namespace extra2d } // namespace extra2d