diff --git a/examples/collision_demo/xmake.lua b/examples/collision_demo/xmake.lua index abf2f2e..284abc0 100644 --- a/examples/collision_demo/xmake.lua +++ b/examples/collision_demo/xmake.lua @@ -10,7 +10,7 @@ local example_dir = os.scriptdir() target("collision_demo") set_kind("binary") add_files("main.cpp") - add_includedirs("../../Extra2D/include") + add_includedirs("../../include") add_deps("extra2d") -- 使用与主项目相同的平台配置 diff --git a/examples/flappy_bird/GameOverLayer.cpp b/examples/flappy_bird/GameOverLayer.cpp index 0215ecd..dcac7d6 100644 --- a/examples/flappy_bird/GameOverLayer.cpp +++ b/examples/flappy_bird/GameOverLayer.cpp @@ -8,6 +8,7 @@ #include "Number.h" #include "ResLoader.h" #include "StartScene.h" +#include namespace flappybird { @@ -25,7 +26,8 @@ void GameOverLayer::onEnter() { float screenHeight = GAME_HEIGHT; // 整体居中(x 坐标相对于屏幕中心) - setPosition(extra2d::Vec2(screenWidth / 2.0f, screenHeight)); + // 初始位置在屏幕底部,然后向上滑出 + setPosition(extra2d::Vec2(screenWidth / 2.0f, screenHeight + 200.0f)); // 显示 "Game Over" 文字(y=120,从顶部开始) auto gameOverFrame = ResLoader::getKeyFrame("text_game_over"); @@ -43,14 +45,23 @@ void GameOverLayer::onEnter() { // 初始化按钮 initButtons(); - // 动画完成,启用按钮 - animationDone_ = true; - if (restartBtn_) - restartBtn_->setEnabled(true); - if (menuBtn_) - menuBtn_->setEnabled(true); - if (shareBtn_) - shareBtn_->setEnabled(true); + // 向上滑出动画 + extra2d::TweenOptions opts; + opts.easing = extra2d::TweenEasing::BackOut; + tween() + .to(0.5f, + extra2d::tween::pos(screenWidth / 2.0f, screenHeight / 2.0f - 300.0f), + opts) + .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) { diff --git a/examples/flappy_bird/bird.cpp b/examples/flappy_bird/bird.cpp index a7cdae9..920d619 100644 --- a/examples/flappy_bird/bird.cpp +++ b/examples/flappy_bird/bird.cpp @@ -8,63 +8,78 @@ namespace flappybird { Bird::Bird() { - // 注意:不要在构造函数中调用 initAnimations() - // 因为此时 weak_from_this() 还不能使用 setStatus(Status::Idle); } +Bird::~Bird() = default; + void Bird::onEnter() { Node::onEnter(); - // 在 onEnter 中初始化动画,此时 weak_from_this() 可用 - if (!animSprite_) { + if (!sprite_) { initAnimations(); } } -Bird::~Bird() = default; - void Bird::initAnimations() { // 随机选择小鸟颜色(0-2) int colorMode = extra2d::randomInt(0, 2); std::string prefix = "bird" + std::to_string(colorMode) + "_"; - // 创建动画片段 - auto clip = extra2d::AnimationClip::create("bird_fly"); - - // 添加动画帧序列: 0 -> 1 -> 2 -> 1 - // 注意:每个颜色只有 0, 1, 2 三个帧,没有 3 + // 加载动画帧序列: 0 -> 1 -> 2 -> 1 int frameSequence[] = {0, 1, 2, 1}; for (int frameIndex : frameSequence) { auto frameSprite = ResLoader::getKeyFrame(prefix + std::to_string(frameIndex)); if (frameSprite) { - extra2d::AnimationFrame frame; - frame.spriteFrame = frameSprite; - frame.delay = 100.0f; // 100毫秒 = 0.1秒 - clip->addFrame(std::move(frame)); + frames_.push_back(frameSprite); } else { E2D_LOG_WARN("无法加载动画帧: {}{}", prefix, frameIndex); } } - // 创建动画精灵 - if (clip->getFrameCount() > 0) { - clip->setLooping(true); - animSprite_ = extra2d::AnimatedSprite::create(clip); - // 精灵图动画不应应用帧变换(避免覆盖节点位置) - animSprite_->setApplyFrameTransform(false); - animSprite_->play(); - addChild(animSprite_); - E2D_LOG_INFO("小鸟动画创建成功: 颜色={}, 帧数={}, running={}, animSprite父节点={}", - colorMode, clip->getFrameCount(), isRunning(), - animSprite_->getParent() ? "有" : "无"); + // 创建精灵 + if (!frames_.empty()) { + sprite_ = extra2d::Sprite::create(); + setCurrentFrame(0); + addChild(sprite_); + E2D_LOG_INFO("小鸟动画创建成功: 颜色={}, 帧数={}", colorMode, frames_.size()); } else { E2D_LOG_ERROR("小鸟动画创建失败: 没有找到任何动画帧"); } } +void Bird::setCurrentFrame(int frameIndex) { + if (frames_.empty() || !sprite_) return; + + frameIndex = frameIndex % static_cast(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(frames_.size())); + } +} + void Bird::onUpdate(float dt) { extra2d::Node::onUpdate(dt); + // 更新帧动画 + updateFrameAnimation(dt); + // 处理闲置动画(上下浮动) if (status_ == Status::Idle) { idleTimer_ += dt; @@ -73,15 +88,15 @@ void Bird::onUpdate(float dt) { } void Bird::onRender(extra2d::RenderBackend& renderer) { - // 动画精灵会自动渲染,这里只需要处理旋转和偏移 - if (animSprite_) { - animSprite_->setRotation(rotation_); + // 精灵会自动渲染,这里只需要处理旋转和偏移 + if (sprite_) { + sprite_->setRotation(rotation_); // 应用闲置偏移 if (status_ == Status::Idle) { - animSprite_->setPosition(extra2d::Vec2(0.0f, idleOffset_)); + sprite_->setPosition(extra2d::Vec2(0.0f, idleOffset_)); } 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) { case Status::Still: // 停止所有动画 - if (animSprite_) { - animSprite_->pause(); - } + frameTimer_ = 0.0f; break; case Status::Idle: // 开始闲置动画 - if (animSprite_) { - animSprite_->setPlaybackSpeed(1.0f); // 正常速度 - animSprite_->play(); - } + frameInterval_ = 0.1f; // 正常速度 idleTimer_ = 0.0f; break; case Status::StartToFly: // 停止闲置动画,加速翅膀扇动 idleOffset_ = 0.0f; - if (animSprite_) { - animSprite_->setPlaybackSpeed(2.0f); // 2倍速度 = 0.05秒每帧 - } break; case Status::Fly: diff --git a/examples/flappy_bird/bird.h b/examples/flappy_bird/bird.h index d7aa951..89a45c1 100644 --- a/examples/flappy_bird/bird.h +++ b/examples/flappy_bird/bird.h @@ -98,15 +98,29 @@ private: */ void initAnimations(); + /** + * @brief 更新帧动画 + */ + void updateFrameAnimation(float dt); + + /** + * @brief 设置当前帧 + */ + void setCurrentFrame(int frameIndex); + bool living_ = true; // 是否存活 float speed_ = 0.0f; // 垂直速度 float rotation_ = 0.0f; // 旋转角度 Status status_ = Status::Idle; // 当前状态 // 动画相关 - extra2d::Ptr animSprite_; // 动画精灵 - float idleTimer_ = 0.0f; // 闲置动画计时器 - float idleOffset_ = 0.0f; // 闲置偏移量 + extra2d::Ptr sprite_; // 精灵 + std::vector> frames_; // 动画帧 + 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; // 重力加速度 diff --git a/examples/flappy_bird/xmake.lua b/examples/flappy_bird/xmake.lua index 335a134..85f1548 100644 --- a/examples/flappy_bird/xmake.lua +++ b/examples/flappy_bird/xmake.lua @@ -10,7 +10,7 @@ local example_dir = os.scriptdir() target("flappy_bird") set_kind("binary") add_files("*.cpp") - add_includedirs("../../Extra2D/include") + add_includedirs("../../include") add_deps("extra2d") -- 使用与主项目相同的平台配置 diff --git a/examples/hello_world/xmake.lua b/examples/hello_world/xmake.lua index 8998521..d21e025 100644 --- a/examples/hello_world/xmake.lua +++ b/examples/hello_world/xmake.lua @@ -10,7 +10,7 @@ local example_dir = os.scriptdir() target("hello_world") set_kind("binary") add_files("main.cpp") - add_includedirs("../../Extra2D/include") + add_includedirs("../../include") add_deps("extra2d") -- 使用与主项目相同的平台配置 diff --git a/examples/push_box/xmake.lua b/examples/push_box/xmake.lua index f550124..6c671c5 100644 --- a/examples/push_box/xmake.lua +++ b/examples/push_box/xmake.lua @@ -10,7 +10,7 @@ local example_dir = os.scriptdir() target("push_box") set_kind("binary") add_files("*.cpp") - add_includedirs("../../Extra2D/include") + add_includedirs("../../include") add_deps("extra2d") -- 使用与主项目相同的平台配置 diff --git a/examples/spatial_index_demo/xmake.lua b/examples/spatial_index_demo/xmake.lua index 307b3e9..4d09d5c 100644 --- a/examples/spatial_index_demo/xmake.lua +++ b/examples/spatial_index_demo/xmake.lua @@ -10,7 +10,7 @@ local example_dir = os.scriptdir() target("spatial_index_demo") set_kind("binary") add_files("main.cpp") - add_includedirs("../../Extra2D/include") + add_includedirs("../../include") add_deps("extra2d") -- 使用与主项目相同的平台配置 diff --git a/include/animation/als_parser.h b/include/animation/als_parser.h deleted file mode 100644 index 379085f..0000000 --- a/include/animation/als_parser.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -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 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 diff --git a/include/animation/ani_binary_parser.h b/include/animation/ani_binary_parser.h deleted file mode 100644 index 9ece1b6..0000000 --- a/include/animation/ani_binary_parser.h +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -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 diff --git a/include/animation/ani_parser.h b/include/animation/ani_parser.h deleted file mode 100644 index bf206a6..0000000 --- a/include/animation/ani_parser.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -namespace extra2d { - -// ============================================================================ -// ANI 文件解析结果 -// ============================================================================ -struct AniParseResult { - bool success = false; - std::string errorMessage; - Ptr 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 diff --git a/include/animation/animated_sprite.h b/include/animation/animated_sprite.h deleted file mode 100644 index 4bbcc29..0000000 --- a/include/animation/animated_sprite.h +++ /dev/null @@ -1,128 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace extra2d { - -// ============================================================================ -// AnimatedSprite - 动画精灵节点 -// 将 AnimationController 与 Sprite 渲染桥接,接入场景图 -// ============================================================================ -class AnimatedSprite : public Sprite { -public: - AnimatedSprite(); - ~AnimatedSprite() override = default; - - // ------ 静态工厂 ------ - static Ptr create(); - static Ptr create(Ptr clip); - static Ptr create(const std::string &aniFilePath); - - // ------ 动画绑定 ------ - void setAnimationClip(Ptr clip); - void loadAnimation(const std::string &aniFilePath); - Ptr getAnimationClip() const; - - // ------ 动画字典 ------ - void addAnimation(const std::string &name, Ptr clip); - void play(const std::string &name, bool loop = true); - bool hasAnimation(const std::string &name) const; - Ptr 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 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> &getCurrentDamageBoxes() const; - const std::vector> &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> animations_; - std::string currentAnimationName_; - static const std::string emptyString_; - - // 帧范围限制(用于精灵图动画) - int frameRangeStart_ = 0; // 起始帧索引 - int frameRangeEnd_ = -1; // 结束帧索引,-1表示不限制 - - // 空碰撞盒列表(用于无帧时返回引用) - static const std::vector> emptyBoxes_; - - void applyFrame(const AnimationFrame &frame); - void onFrameChanged(size_t oldIdx, size_t newIdx, - const AnimationFrame &frame); -}; - -} // namespace extra2d diff --git a/include/animation/animation_cache.h b/include/animation/animation_cache.h deleted file mode 100644 index 5befd1c..0000000 --- a/include/animation/animation_cache.h +++ /dev/null @@ -1,102 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -namespace extra2d { - -// 路径替换回调(对应原始 AdditionalOptions) -using PathResolveCallback = std::function; - -// ============================================================================ -// AnimationCache - 动画片段全局缓存(借鉴 Cocos AnimationCache) -// 同一 ANI 文件只解析一次,后续直接复用数据 -// ============================================================================ -class AnimationCache { -public: - static AnimationCache &getInstance() { - static AnimationCache instance; - return instance; - } - - // ------ 加载与获取 ------ - - /// 从文件加载(自动缓存),已缓存则直接返回 - /// 注意:实际的 ANI 解析逻辑在 AniParser 中实现 - /// 此方法在 animation_cache.cpp 中实现,依赖 AniParser - Ptr loadClip(const std::string &aniFilePath); - - /// 从缓存获取(不触发加载) - Ptr getClip(const std::string &name) const { - std::lock_guard lock(mutex_); - auto it = clips_.find(name); - if (it != clips_.end()) - return it->second; - return nullptr; - } - - /// 手动添加到缓存 - void addClip(Ptr clip, const std::string &name) { - std::lock_guard lock(mutex_); - clips_[name] = std::move(clip); - } - - // ------ 缓存管理 ------ - - bool has(const std::string &name) const { - std::lock_guard lock(mutex_); - return clips_.find(name) != clips_.end(); - } - - void removeClip(const std::string &name) { - std::lock_guard lock(mutex_); - clips_.erase(name); - } - - /// 移除未被外部引用的动画片段 - void removeUnusedClips() { - std::lock_guard 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 lock(mutex_); - clips_.clear(); - } - - size_t count() const { - std::lock_guard 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> clips_; - PathResolveCallback pathResolver_; -}; - -// 便捷宏 -#define E2D_ANIMATION_CACHE() ::extra2d::AnimationCache::getInstance() - -} // namespace extra2d diff --git a/include/animation/animation_clip.h b/include/animation/animation_clip.h deleted file mode 100644 index 1224226..0000000 --- a/include/animation/animation_clip.h +++ /dev/null @@ -1,184 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -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(index), frame); - } - - void removeFrame(size_t index) { - assert(index < frames_.size()); - frames_.erase(frames_.begin() + static_cast(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(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 create(const std::string &name = "") { - return makePtr(name); - } - - /// 从精灵图网格创建(所有帧按顺序) - static Ptr createFromGrid(Ptr 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(); - 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(margin + col * (frameWidth + spacing)), - static_cast(margin + flippedRow * (frameHeight + spacing)), - static_cast(frameWidth), static_cast(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 - createFromGridIndices(Ptr texture, int frameWidth, int frameHeight, - const std::vector &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(); - for (int idx : frameIndices) { - int col = idx % cols; - int row = idx / cols; - - // 翻转行顺序:精灵图第0行在顶部,但OpenGL纹理V坐标从底部开始 - int flippedRow = (rows - 1) - row; - - Rect rect( - static_cast(margin + col * (frameWidth + spacing)), - static_cast(margin + flippedRow * (frameHeight + spacing)), - static_cast(frameWidth), static_cast(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 frames_; - FramePropertySet globalProperties_; -}; - -} // namespace extra2d diff --git a/include/animation/animation_controller.h b/include/animation/animation_controller.h deleted file mode 100644 index dff514e..0000000 --- a/include/animation/animation_controller.h +++ /dev/null @@ -1,108 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -namespace extra2d { - -// ============================================================================ -// 动画播放状态 -// ============================================================================ -enum class AnimPlayState : uint8 { Stopped, Playing, Paused }; - -// ============================================================================ -// AnimationController - 动画播放控制器 -// 借鉴 Cocos Creator 的 AnimationState:纯播放逻辑,不持有渲染资源 -// ============================================================================ -class AnimationController { -public: - // 回调类型定义 - using FrameChangeCallback = std::function; - using KeyframeCallback = std::function; - using SoundTriggerCallback = std::function; - using CompletionCallback = std::function; - - AnimationController() = default; - - // ------ 绑定动画数据 ------ - void setClip(Ptr clip); - Ptr 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 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 diff --git a/include/animation/animation_event.h b/include/animation/animation_event.h deleted file mode 100644 index f94af58..0000000 --- a/include/animation/animation_event.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include -#include -#include - -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; -using KeyframeHitCallback = std::function; -using AnimationCompleteCallback = std::function; - -} // namespace extra2d diff --git a/include/animation/animation_frame.h b/include/animation/animation_frame.h deleted file mode 100644 index c072311..0000000 --- a/include/animation/animation_frame.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace extra2d { - -// ============================================================================ -// AnimationFrame - 单帧数据 -// 引用 SpriteFrame 而非直接持有纹理(借鉴 Cocos 模式) -// 通过 FramePropertySet 支持不固定数据(ANI Flag 系统增强版) -// ============================================================================ -struct AnimationFrame { - // ------ 核心数据(固定部分)------ - Ptr spriteFrame; // 精灵帧引用(Cocos 模式) - std::string texturePath; // 原始图片路径(用于解析时定位资源) - int textureIndex = 0; // 精灵图集索引 - Vec2 offset; // 位置偏移 - float delay = 100.0f; // 帧延迟(毫秒) - - // ------ 碰撞盒数据(DNF ANI 格式)------ - std::vector> damageBoxes; // 伤害碰撞盒 - std::vector> attackBoxes; // 攻击碰撞盒 - - // ------ 不固定数据(属性集合)------ - FramePropertySet properties; // 类型安全的 Flag 系统 - - // ------ 便捷方法 ------ - bool hasTexture() const { - return spriteFrame != nullptr && spriteFrame->isValid(); - } - - bool hasInterpolation() const { - return properties.getOr(FramePropertyKey::Interpolation, false); - } - - bool hasKeyframeCallback() const { - return properties.has(FramePropertyKey::SetFlag); - } - - int getKeyframeIndex() const { - return properties.getOr(FramePropertyKey::SetFlag, -1); - } - - Vec2 getEffectiveScale() const { - return properties.getOr(FramePropertyKey::ImageRate, Vec2::One()); - } - - float getEffectiveRotation() const { - return properties.getOr(FramePropertyKey::ImageRotate, 0.0f); - } - - Color getEffectiveColor() const { - return properties.getOr(FramePropertyKey::ColorTint, Colors::White); - } -}; - -} // namespace extra2d diff --git a/include/animation/animation_node.h b/include/animation/animation_node.h deleted file mode 100644 index 0327d87..0000000 --- a/include/animation/animation_node.h +++ /dev/null @@ -1,108 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -namespace extra2d { - -// ============================================================================ -// AnimationNode - 动画节点(继承 Node) -// 使用 FrameRenderer 单渲染器策略,不依赖 Sprite 基类 -// 适用于需要独立渲染控制的动画(如特效、复合动画图层) -// ============================================================================ -class AnimationNode : public Node { -public: - AnimationNode(); - ~AnimationNode() override = default; - - // ------ 静态工厂(Cocos 风格)------ - static Ptr create(); - static Ptr create(Ptr clip); - static Ptr create(const std::string &aniFilePath); - - // ------ 动画数据 ------ - void setClip(Ptr clip); - Ptr 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> &getCurrentDamageBoxes() const; - const std::vector> &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 eventListeners_; - - static const std::vector> emptyBoxes_; - - void setupControllerCallbacks(); - void dispatchEvent(const AnimationEvent &event); -}; - -} // namespace extra2d diff --git a/include/animation/composite_animation.h b/include/animation/composite_animation.h deleted file mode 100644 index 0374eea..0000000 --- a/include/animation/composite_animation.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -namespace extra2d { - -// ============================================================================ -// CompositeAnimation - ALS 多层复合动画节点 -// 管理多个 AnimationNode 图层,统一控制播放 -// 对应 DNF 的 ALS 格式(多层动画叠加) -// ============================================================================ -class CompositeAnimation : public Node { -public: - CompositeAnimation() = default; - ~CompositeAnimation() override = default; - - // ------ 静态工厂 ------ - static Ptr create(); - static Ptr create(const std::string &alsFilePath); - - // ------ 加载 ------ - bool loadFromFile(const std::string &alsFilePath); - - // ------ 图层管理 ------ - void addLayer(Ptr node, int zOrder = 0); - void removeLayer(size_t index); - Ptr getLayer(size_t index) const; - Ptr 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 node; - int zOrder = 0; - }; - std::vector layers_; -}; - -} // namespace extra2d diff --git a/include/animation/frame_property.h b/include/animation/frame_property.h deleted file mode 100644 index 7a71407..0000000 --- a/include/animation/frame_property.h +++ /dev/null @@ -1,210 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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: 裁剪区域 [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 使用索引引用 -}; - -// 紧凑的属性值结构(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{}(static_cast(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& value); - - void setCustom(const std::string &key, std::any value); - - // ------ 类型安全获取 ------ - template std::optional get(FramePropertyKey key) const; - - template - T getOr(FramePropertyKey key, const T &defaultValue) const { - auto result = get(key); - return result.value_or(defaultValue); - } - - std::optional 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; - 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 customProperties_; - - // 字符串池和vector池,用于存储大对象 - mutable std::vector stringPool_; - mutable std::vector> vectorPool_; - mutable uint32_t nextStringIndex_ = 0; - mutable uint32_t nextVectorIndex_ = 0; - - uint32_t allocateString(const std::string& str); - uint32_t allocateVector(const std::vector& vec); - const std::string* getString(uint32_t index) const; - const std::vector* getVector(uint32_t index) const; -}; - -// 模板特化声明 -template <> std::optional FramePropertySet::get(FramePropertyKey key) const; -template <> std::optional FramePropertySet::get(FramePropertyKey key) const; -template <> std::optional FramePropertySet::get(FramePropertyKey key) const; -template <> std::optional FramePropertySet::get(FramePropertyKey key) const; -template <> std::optional FramePropertySet::get(FramePropertyKey key) const; -template <> std::optional FramePropertySet::get(FramePropertyKey key) const; -template <> std::optional> FramePropertySet::get>(FramePropertyKey key) const; - -} // namespace extra2d diff --git a/include/animation/frame_renderer.h b/include/animation/frame_renderer.h deleted file mode 100644 index 6220c0a..0000000 --- a/include/animation/frame_renderer.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace extra2d { - -// ============================================================================ -// FrameRenderer - 帧渲染器 -// 单渲染器 + SpriteFrame 引用策略,替代 N帧=N个Sprite 的旧设计 -// 负责预加载帧的 SpriteFrame、渲染当前帧、处理混合模式 -// ============================================================================ -class FrameRenderer { -public: - FrameRenderer() = default; - - // ------ 预加载 ------ - // 解析所有帧的 SpriteFrame(通过 SpriteFrameCache) - bool preloadFrames(const std::vector &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 getSpriteFrame(size_t frameIndex) const; - Size getMaxFrameSize() const { return maxFrameSize_; } - bool isLoaded() const { return !spriteFrames_.empty(); } - -private: - std::vector> spriteFrames_; - Size maxFrameSize_; - - void drawSpriteFrame(RenderBackend &renderer, Ptr 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 diff --git a/include/animation/interpolation_engine.h b/include/animation/interpolation_engine.h deleted file mode 100644 index d612979..0000000 --- a/include/animation/interpolation_engine.h +++ /dev/null @@ -1,106 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -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 diff --git a/include/animation/tween.h b/include/animation/tween.h new file mode 100644 index 0000000..4859703 --- /dev/null +++ b/include/animation/tween.h @@ -0,0 +1,168 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +class Node; + +struct TweenOptions { + TweenEasing easing = TweenEasing::Linear; + std::function onStart = nullptr; + std::function onUpdate = nullptr; + std::function onComplete = nullptr; +}; + +struct TweenProperty { + std::optional position; + std::optional scale; + std::optional rotation; + std::optional opacity; + std::optional 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; + using Callback = std::function; + + 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 ¶llel(); + 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> 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> parallelActions_; + + void addAction(std::unique_ptr 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 props); + +} // namespace tween + +} // namespace extra2d diff --git a/include/animation/tween_easing.h b/include/animation/tween_easing.h new file mode 100644 index 0000000..39e9dd3 --- /dev/null +++ b/include/animation/tween_easing.h @@ -0,0 +1,91 @@ +#pragma once + +#include + +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 diff --git a/include/extra2d.h b/include/extra2d.h index 4679c09..7eda29c 100644 --- a/include/extra2d.h +++ b/include/extra2d.h @@ -39,20 +39,8 @@ // Animation #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include // UI #include diff --git a/include/platform/input.h b/include/platform/input.h index 11dd1ca..b2101a3 100644 --- a/include/platform/input.h +++ b/include/platform/input.h @@ -9,9 +9,6 @@ namespace extra2d { -// ============================================================================ -// 鼠标按钮枚举 -// ============================================================================ enum class MouseButton { Left = 0, Right = 1, @@ -24,43 +21,27 @@ enum class MouseButton { Count = 8 }; -// ============================================================================ -// Input 类 - 跨平台输入管理 -// 支持: 键盘、鼠标、手柄、触摸屏 -// ============================================================================ class Input { public: Input(); ~Input(); - // 初始化 void init(); void shutdown(); - // 每帧更新 void update(); - // ------------------------------------------------------------------------ - // 键盘输入 - // ------------------------------------------------------------------------ bool isKeyDown(int keyCode) const; bool isKeyPressed(int keyCode) const; bool isKeyReleased(int keyCode) const; - // ------------------------------------------------------------------------ - // 手柄按钮 - // ------------------------------------------------------------------------ bool isButtonDown(int button) const; bool isButtonPressed(int button) const; bool isButtonReleased(int button) const; - // 摇杆 Vec2 getLeftStick() const; Vec2 getRightStick() const; - // ------------------------------------------------------------------------ - // 鼠标输入 - // ------------------------------------------------------------------------ bool isMouseDown(MouseButton button) const; bool isMousePressed(MouseButton button) const; bool isMouseReleased(MouseButton button) const; @@ -74,40 +55,34 @@ public: void setMouseVisible(bool visible); void setMouseLocked(bool locked); - // ------------------------------------------------------------------------ - // 触摸屏 (Switch 原生支持,PC 端模拟或禁用) - // ------------------------------------------------------------------------ bool isTouching() const { return touching_; } Vec2 getTouchPosition() const { return touchPosition_; } int getTouchCount() const { return touchCount_; } - // ------------------------------------------------------------------------ - // 便捷方法 - // ------------------------------------------------------------------------ bool isAnyKeyDown() const; bool isAnyMouseDown() const; + void onControllerAdded(int deviceIndex); + void onControllerRemoved(int instanceId); + void onMouseWheel(float x, float y); + private: static constexpr int MAX_BUTTONS = SDL_CONTROLLER_BUTTON_MAX; static constexpr int MAX_KEYS = SDL_NUM_SCANCODES; SDL_GameController *controller_; - // 键盘状态 (PC 端使用) std::array keysDown_; std::array prevKeysDown_; - // 手柄按钮状态 std::array buttonsDown_; std::array prevButtonsDown_; - // 摇杆状态 float leftStickX_; float leftStickY_; float rightStickX_; float rightStickY_; - // 鼠标状态 (PC 端使用) Vec2 mousePosition_; Vec2 prevMousePosition_; float mouseScroll_; @@ -115,26 +90,15 @@ private: std::array mouseButtonsDown_; std::array prevMouseButtonsDown_; - // 触摸屏状态 (Switch 原生) bool touching_; bool prevTouching_; Vec2 touchPosition_; Vec2 prevTouchPosition_; int touchCount_; - // 映射键盘 keyCode 到 SDL GameController 按钮 (Switch 兼容模式) - SDL_GameControllerButton mapKeyToButton(int keyCode) const; - - // 更新键盘状态 void updateKeyboard(); - - // 更新鼠标状态 void updateMouse(); - - // 更新手柄状态 void updateGamepad(); - - // 更新触摸屏状态 void updateTouch(); }; diff --git a/include/scene/node.h b/include/scene/node.h index b1b1e64..e4d8304 100644 --- a/include/scene/node.h +++ b/include/scene/node.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -16,6 +17,8 @@ namespace extra2d { class Scene; class RenderBackend; struct RenderCommand; +class Tween; +enum class TweenEasing : uint8_t; // ============================================================================ // 节点基类 - 场景图的基础 @@ -159,6 +162,83 @@ public: // ------------------------------------------------------------------------ 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(0)); + + /** + * @brief 移动指定距离 + */ + Tween &moveBy(const Vec2 &delta, float duration, + TweenEasing easing = static_cast(0)); + + /** + * @brief 缩放到目标值 + */ + Tween &scaleTo(float scale, float duration, + TweenEasing easing = static_cast(0)); + + /** + * @brief 缩放指定增量 + */ + Tween &scaleBy(float delta, float duration, + TweenEasing easing = static_cast(0)); + + /** + * @brief 旋转到目标角度 + */ + Tween &rotateTo(float degrees, float duration, + TweenEasing easing = static_cast(0)); + + /** + * @brief 旋转指定角度 + */ + Tween &rotateBy(float degrees, float duration, + TweenEasing easing = static_cast(0)); + + /** + * @brief 淡入(透明度从当前到1) + */ + Tween &fadeIn(float duration, TweenEasing easing = static_cast(0)); + + /** + * @brief 淡出(透明度从当前到0) + */ + Tween &fadeOut(float duration, TweenEasing easing = static_cast(0)); + + /** + * @brief 透明度变化到目标值 + */ + Tween &fadeTo(float opacity, float duration, + TweenEasing easing = static_cast(0)); + + /** + * @brief 停止所有 Tween 动画 + */ + void stopAllTweens(); + + /** + * @brief 更新所有 Tween 动画 + */ + void updateTweens(float dt); + // ------------------------------------------------------------------------ // 内部方法 // ------------------------------------------------------------------------ @@ -246,7 +326,9 @@ private: bool visible_ = true; // 1 byte bool running_ = false; // 1 byte bool spatialIndexed_ = true; // 1 byte - // 填充 2 bytes 到 8 字节对齐 + + // 13. Tween 动画列表 + std::vector> tweens_; }; } // namespace extra2d diff --git a/src/animation/als_parser.cpp b/src/animation/als_parser.cpp deleted file mode 100644 index 9b767e1..0000000 --- a/src/animation/als_parser.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include -#include - -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 diff --git a/src/animation/ani_binary_parser.cpp b/src/animation/ani_binary_parser.cpp deleted file mode 100644 index ee8c628..0000000 --- a/src/animation/ani_binary_parser.cpp +++ /dev/null @@ -1,313 +0,0 @@ -#include -#include -#include -#include -#include -#include - -namespace extra2d { - -namespace { - -// 简易二进制缓冲区读取器 -class BufferReader { -public: - BufferReader(const uint8_t *data, size_t length) - : data_(data), length_(length), pos_(0) {} - - template 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(len) > length_) { - return ""; - } - std::string result(reinterpret_cast(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(std::tolower(static_cast(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 resourceCount = reader.read(); - - // 读取精灵路径列表 - std::vector sprites; - sprites.reserve(resourceCount); - for (uint16_t i = 0; i < resourceCount; ++i) { - int32_t len = reader.read(); - std::string path = reader.readAsciiString(len); - toLower(path); - sprites.push_back(std::move(path)); - } - - // 读取全局参数 - uint16_t globalParamCount = reader.read(); - for (uint16_t j = 0; j < globalParamCount; ++j) { - uint16_t type = reader.read(); - switch (type) { - case static_cast(AniNodeType::Loop): - if (reader.read()) { - result.clip->setLooping(true); - } - break; - case static_cast(AniNodeType::Shadow): - if (reader.read()) { - 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(); - for (uint16_t j = 0; j < boxCount; ++j) { - uint16_t boxType = reader.read(); - std::array box; - for (int m = 0; m < 6; ++m) { - box[m] = reader.read(); - } - if (boxType == static_cast(AniNodeType::DamageBox)) { - frame.damageBoxes.push_back(box); - } else if (boxType == static_cast(AniNodeType::AttackBox)) { - frame.attackBoxes.push_back(box); - } - } - - // 图片 ID 和参数 - uint16_t imgId = reader.read(); - uint16_t imgParam = reader.read(); - (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(reader.read()), - static_cast(reader.read())); - - // 帧属性 - uint16_t propertyCount = reader.read(); - for (uint16_t m = 0; m < propertyCount; ++m) { - uint16_t propType = reader.read(); - AniNodeType nodeType = static_cast(propType); - - switch (nodeType) { - case AniNodeType::Loop: - frame.properties.set(FramePropertyKey::Loop, - static_cast(reader.read())); - break; - - case AniNodeType::Shadow: - frame.properties.set(FramePropertyKey::Shadow, - static_cast(reader.read())); - break; - - case AniNodeType::Interpolation: - frame.properties.withInterpolation( - static_cast(reader.read())); - break; - - case AniNodeType::Coord: - frame.properties.set(FramePropertyKey::Coord, - static_cast(reader.read())); - break; - - case AniNodeType::ImageRate: { - float rateX = reader.read(); - float rateY = reader.read(); - frame.properties.withImageRate(Vec2(rateX, rateY)); - break; - } - - case AniNodeType::ImageRotate: - frame.properties.withImageRotate( - static_cast(reader.read())); - break; - - case AniNodeType::RGBA: { - uint32_t rgba = reader.read(); - uint8_t a = static_cast((rgba >> 24) & 0xFF); - uint8_t r = static_cast((rgba >> 16) & 0xFF); - uint8_t g = static_cast((rgba >> 8) & 0xFF); - uint8_t b = static_cast((rgba) & 0xFF); - frame.properties.withColorTint(Color::fromRGBA(r, g, b, a)); - break; - } - - case AniNodeType::GraphicEffect: { - uint16_t effectType = reader.read(); - frame.properties.set(FramePropertyKey::GraphicEffect, - static_cast(effectType)); - // MONOCHROME 额外读取 rgb - if (effectType == 5) { - reader.read(); // r - reader.read(); // g - reader.read(); // b - } - // SPACEDISTORT 额外读取 pos - else if (effectType == 6) { - reader.read(); // x - reader.read(); // y - } - break; - } - - case AniNodeType::Delay: - frame.delay = static_cast(reader.read()); - break; - - case AniNodeType::DamageType: - frame.properties.set(FramePropertyKey::DamageType, - static_cast(reader.read())); - break; - - case AniNodeType::PlaySound: { - int32_t len = reader.read(); - std::string soundPath = reader.readAsciiString(len); - frame.properties.withPlaySound(resolvePath(soundPath)); - break; - } - - case AniNodeType::SetFlag: - frame.properties.withSetFlag(reader.read()); - break; - - case AniNodeType::FlipType: - frame.properties.set(FramePropertyKey::FlipType, - static_cast(reader.read())); - break; - - case AniNodeType::LoopStart: - frame.properties.set(FramePropertyKey::LoopStart, true); - break; - - case AniNodeType::LoopEnd: - frame.properties.set(FramePropertyKey::LoopEnd, reader.read()); - break; - - case AniNodeType::Clip: { - std::vector clipRegion = { - static_cast(reader.read()), - static_cast(reader.read()), - static_cast(reader.read()), - static_cast(reader.read())}; - 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 buffer(static_cast(size)); - file.read(reinterpret_cast(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 diff --git a/src/animation/ani_parser.cpp b/src/animation/ani_parser.cpp deleted file mode 100644 index f2f9fc8..0000000 --- a/src/animation/ani_parser.cpp +++ /dev/null @@ -1,428 +0,0 @@ -#include -#include -#include -#include -#include - -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 splitWhitespace(const std::string &s) { - std::vector 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(file)), - std::istreambuf_iterator()); - 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(std::stoi(tokens[0])), - static_cast(std::stoi(tokens[1]))); - } - continue; - } - - // [DELAY] - if (lineStartsWith(trimmed, "[DELAY]")) { - std::string val = readValue(trimmed, "[DELAY]", stream); - currentFrame.delay = static_cast(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 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 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(std::stoi(tokens[0])), - static_cast(std::stoi(tokens[1])), - static_cast(std::stoi(tokens[2])), - static_cast(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 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 diff --git a/src/animation/animated_sprite.cpp b/src/animation/animated_sprite.cpp deleted file mode 100644 index 5d26e90..0000000 --- a/src/animation/animated_sprite.cpp +++ /dev/null @@ -1,290 +0,0 @@ -#include -#include - -namespace extra2d { - -const std::vector> 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::create() { - return makePtr(); -} - -Ptr AnimatedSprite::create(Ptr clip) { - auto sprite = makePtr(); - sprite->setAnimationClip(std::move(clip)); - return sprite; -} - -Ptr AnimatedSprite::create(const std::string &aniFilePath) { - auto sprite = makePtr(); - sprite->loadAnimation(aniFilePath); - return sprite; -} - -// ------ 动画绑定 ------ - -void AnimatedSprite::setAnimationClip(Ptr 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 AnimatedSprite::getAnimationClip() const { - return controller_.getClip(); -} - -// ------ 动画字典 ------ - -void AnimatedSprite::addAnimation(const std::string &name, - Ptr 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 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(start); - size_t maxFrame = - (end < 0) ? clip->getFrameCount() - 1 : static_cast(end); - - if (currentFrame < minFrame || currentFrame > maxFrame) { - controller_.setFrameIndex(minFrame); - } - } -} - -std::pair 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> & -AnimatedSprite::getCurrentDamageBoxes() const { - auto clip = controller_.getClip(); - if (!clip || clip->empty()) - return emptyBoxes_; - return clip->getFrame(controller_.getCurrentFrameIndex()).damageBoxes; -} - -const std::vector> & -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(frameRangeStart_); - size_t maxFrame = static_cast(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(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 diff --git a/src/animation/animation_cache.cpp b/src/animation/animation_cache.cpp deleted file mode 100644 index b50021a..0000000 --- a/src/animation/animation_cache.cpp +++ /dev/null @@ -1,103 +0,0 @@ -#include -#include -#include -#include -#include - -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(buf[0]) == 0xEF && - static_cast(buf[1]) == 0xBB && - static_cast(buf[2]) == 0xBF) { - pos = 3; - } - - // 跳过空白 - while (pos < static_cast(bytesRead) && - (buf[pos] == ' ' || buf[pos] == '\t' || buf[pos] == '\r' || - buf[pos] == '\n')) { - ++pos; - } - - if (pos >= static_cast(bytesRead)) - return false; - - // 文本 ANI 文件以 '#' (注释/PVF_File) 或 '[' (标签) 开头 - return buf[pos] == '#' || buf[pos] == '['; -} - -} // anonymous namespace - -Ptr AnimationCache::loadClip(const std::string &aniFilePath) { - // 先检查缓存 - { - std::lock_guard 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 lock(mutex_); - clips_[aniFilePath] = result.clip; - } - - return result.clip; -} - -} // namespace extra2d diff --git a/src/animation/animation_clip.cpp b/src/animation/animation_clip.cpp deleted file mode 100644 index 43c2226..0000000 --- a/src/animation/animation_clip.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include - -namespace extra2d { - -// AnimationClip 的实现全部在头文件中以内联方式完成 -// 此文件保留用于未来可能需要的非内联实现 - -} // namespace extra2d diff --git a/src/animation/animation_controller.cpp b/src/animation/animation_controller.cpp deleted file mode 100644 index 22ad90e..0000000 --- a/src/animation/animation_controller.cpp +++ /dev/null @@ -1,209 +0,0 @@ -#include -#include - -namespace extra2d { - -void AnimationController::setClip(Ptr 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 ¤tFrame = 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(FramePropertyKey::SetFlag); - if (flagIndex.has_value() && onKeyframe_) { - onKeyframe_(flagIndex.value()); - } - } - - // 音效触发 - if (props.has(FramePropertyKey::PlaySound)) { - auto soundPath = props.get(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 ¤tFrame = 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 diff --git a/src/animation/animation_frame.cpp b/src/animation/animation_frame.cpp deleted file mode 100644 index 68abc56..0000000 --- a/src/animation/animation_frame.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include - -namespace extra2d { - -// AnimationFrame 的实现全部在头文件中以内联方式完成 -// 此文件保留用于未来可能需要的非内联实现 - -} // namespace extra2d diff --git a/src/animation/animation_node.cpp b/src/animation/animation_node.cpp deleted file mode 100644 index b52c7ba..0000000 --- a/src/animation/animation_node.cpp +++ /dev/null @@ -1,266 +0,0 @@ -#include -#include - -namespace extra2d { - -const std::vector> AnimationNode::emptyBoxes_; - -// ============================================================================ -// 构造 -// ============================================================================ - -AnimationNode::AnimationNode() { setupControllerCallbacks(); } - -// ============================================================================ -// 静态工厂 -// ============================================================================ - -Ptr AnimationNode::create() { return makePtr(); } - -Ptr AnimationNode::create(Ptr clip) { - auto node = makePtr(); - node->setClip(std::move(clip)); - return node; -} - -Ptr AnimationNode::create(const std::string &aniFilePath) { - auto node = makePtr(); - node->loadFromFile(aniFilePath); - return node; -} - -// ============================================================================ -// 动画数据 -// ============================================================================ - -void AnimationNode::setClip(Ptr clip) { - controller_.setClip(clip); - if (clip && !clip->empty()) { - // 预加载所有帧的 SpriteFrame - std::vector 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 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> & -AnimationNode::getCurrentDamageBoxes() const { - auto clip = controller_.getClip(); - if (!clip || clip->empty()) - return emptyBoxes_; - return clip->getFrame(controller_.getCurrentFrameIndex()).damageBoxes; -} - -const std::vector> & -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 diff --git a/src/animation/composite_animation.cpp b/src/animation/composite_animation.cpp deleted file mode 100644 index 82ce4e6..0000000 --- a/src/animation/composite_animation.cpp +++ /dev/null @@ -1,200 +0,0 @@ -#include -#include - -namespace extra2d { - -// ============================================================================ -// 静态工厂 -// ============================================================================ - -Ptr CompositeAnimation::create() { - return makePtr(); -} - -Ptr -CompositeAnimation::create(const std::string &alsFilePath) { - auto comp = makePtr(); - 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 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(index)); -} - -Ptr CompositeAnimation::getLayer(size_t index) const { - if (index >= layers_.size()) - return nullptr; - return layers_[index].node; -} - -Ptr 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 diff --git a/src/animation/frame_property.cpp b/src/animation/frame_property.cpp deleted file mode 100644 index 28de721..0000000 --- a/src/animation/frame_property.cpp +++ /dev/null @@ -1,220 +0,0 @@ -#include - -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& 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 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(stringPool_.size()); - stringPool_.push_back(str); - return index; -} - -uint32_t FramePropertySet::allocateVector(const std::vector& vec) { - // 查找是否已存在相同vector - for (uint32_t i = 0; i < vectorPool_.size(); ++i) { - if (vectorPool_[i] == vec) { - return i; - } - } - // 分配新vector - uint32_t index = static_cast(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* FramePropertySet::getVector(uint32_t index) const { - if (index < vectorPool_.size()) { - return &vectorPool_[index]; - } - return nullptr; -} - -// ============================================================================ -// 模板特化实现 -// ============================================================================ - -template <> std::optional FramePropertySet::get(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 FramePropertySet::get(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 FramePropertySet::get(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 FramePropertySet::get(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 FramePropertySet::get(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 FramePropertySet::get(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> FramePropertySet::get>(FramePropertyKey key) const { - auto it = properties_.find(key); - if (it == properties_.end()) return std::nullopt; - if (it->second.type == PropertyValueType::IntVector) { - const std::vector* 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 diff --git a/src/animation/frame_renderer.cpp b/src/animation/frame_renderer.cpp deleted file mode 100644 index c16430f..0000000 --- a/src/animation/frame_renderer.cpp +++ /dev/null @@ -1,168 +0,0 @@ -#include -#include - -namespace extra2d { - -// ============================================================================ -// 预加载 -// ============================================================================ - -bool FrameRenderer::preloadFrames(const std::vector &frames) { - releaseFrames(); - spriteFrames_.reserve(frames.size()); - maxFrameSize_ = Size{0.0f, 0.0f}; - - for (const auto &frame : frames) { - Ptr 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(FramePropertyKey::BlendAdditive); - if (val.has_value() && val.value()) - return BlendMode::Additive; - } - if (props.has(FramePropertyKey::BlendLinearDodge)) { - auto val = props.get(FramePropertyKey::BlendLinearDodge); - if (val.has_value() && val.value()) - return BlendMode::Additive; // 线性减淡 ≈ 加法混合 - } - return BlendMode::Alpha; -} - -// ============================================================================ -// 查询 -// ============================================================================ - -Ptr FrameRenderer::getSpriteFrame(size_t frameIndex) const { - if (frameIndex >= spriteFrames_.size()) - return nullptr; - return spriteFrames_[frameIndex]; -} - -// ============================================================================ -// 内部绘制 -// ============================================================================ - -void FrameRenderer::drawSpriteFrame(RenderBackend &renderer, - Ptr 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 diff --git a/src/animation/interpolation_engine.cpp b/src/animation/interpolation_engine.cpp deleted file mode 100644 index c403171..0000000 --- a/src/animation/interpolation_engine.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include - -namespace extra2d { - -// InterpolationEngine 的实现全部在头文件中以静态内联方式完成 -// 此文件保留用于未来可能需要的非内联实现 - -} // namespace extra2d diff --git a/src/animation/tween.cpp b/src/animation/tween.cpp new file mode 100644 index 0000000..2e56e51 --- /dev/null +++ b/src/animation/tween.cpp @@ -0,0 +1,451 @@ +#include +#include +#include +#include + +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 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 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 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> 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(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(target_, duration, props, options, true); + addAction(std::move(action)); + return *this; +} + +Tween &Tween::set(const TweenProperty &props) { + auto action = std::make_unique(target_, props); + addAction(std::move(action)); + return *this; +} + +Tween &Tween::delay(float seconds) { + auto action = std::make_unique(seconds); + addAction(std::move(action)); + return *this; +} + +Tween &Tween::call(Callback callback) { + auto action = std::make_unique(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(); + 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(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( + 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 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 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 diff --git a/src/animation/tween_easing.cpp b/src/animation/tween_easing.cpp new file mode 100644 index 0000000..eda757c --- /dev/null +++ b/src/animation/tween_easing.cpp @@ -0,0 +1,236 @@ +#include +#include + +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 diff --git a/src/platform/input.cpp b/src/platform/input.cpp index ed33345..ad25bc2 100644 --- a/src/platform/input.cpp +++ b/src/platform/input.cpp @@ -5,13 +5,11 @@ namespace extra2d { Input::Input() - : controller_(nullptr), - leftStickX_(0.0f), leftStickY_(0.0f), - rightStickX_(0.0f), rightStickY_(0.0f), - mouseScroll_(0.0f), prevMouseScroll_(0.0f), - touching_(false), prevTouching_(false), touchCount_(0) { - - // 初始化所有状态数组 + : controller_(nullptr), leftStickX_(0.0f), leftStickY_(0.0f), + rightStickX_(0.0f), rightStickY_(0.0f), mouseScroll_(0.0f), + prevMouseScroll_(0.0f), touching_(false), prevTouching_(false), + touchCount_(0) { + keysDown_.fill(false); prevKeysDown_.fill(false); buttonsDown_.fill(false); @@ -23,7 +21,6 @@ Input::Input() Input::~Input() { shutdown(); } void Input::init() { - // 打开第一个可用的游戏控制器 for (int i = 0; i < SDL_NumJoysticks(); ++i) { if (SDL_IsGameController(i)) { controller_ = SDL_GameControllerOpen(i); @@ -39,13 +36,10 @@ void Input::init() { E2D_LOG_WARN("No game controller found"); } - // PC 端获取初始鼠标状态 -#ifndef PLATFORM_SWITCH int mouseX, mouseY; SDL_GetMouseState(&mouseX, &mouseY); mousePosition_ = Vec2(static_cast(mouseX), static_cast(mouseY)); prevMousePosition_ = mousePosition_; -#endif } void Input::shutdown() { @@ -56,7 +50,6 @@ void Input::shutdown() { } void Input::update() { - // 保存上一帧状态 prevKeysDown_ = keysDown_; prevButtonsDown_ = buttonsDown_; prevMouseButtonsDown_ = mouseButtonsDown_; @@ -65,7 +58,6 @@ void Input::update() { prevTouching_ = touching_; prevTouchPosition_ = touchPosition_; - // 更新各输入设备状态 updateKeyboard(); updateMouse(); updateGamepad(); @@ -73,50 +65,44 @@ void Input::update() { } void Input::updateKeyboard() { - // 获取当前键盘状态 - const Uint8* state = SDL_GetKeyboardState(nullptr); + const Uint8 *state = SDL_GetKeyboardState(nullptr); for (int i = 0; i < MAX_KEYS; ++i) { keysDown_[i] = state[i] != 0; } } void Input::updateMouse() { -#ifndef PLATFORM_SWITCH - // 更新鼠标位置 int mouseX, mouseY; Uint32 buttonState = SDL_GetMouseState(&mouseX, &mouseY); mousePosition_ = Vec2(static_cast(mouseX), static_cast(mouseY)); - // 更新鼠标按钮状态 mouseButtonsDown_[0] = (buttonState & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0; mouseButtonsDown_[1] = (buttonState & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0; mouseButtonsDown_[2] = (buttonState & SDL_BUTTON(SDL_BUTTON_MIDDLE)) != 0; mouseButtonsDown_[3] = (buttonState & SDL_BUTTON(SDL_BUTTON_X1)) != 0; mouseButtonsDown_[4] = (buttonState & SDL_BUTTON(SDL_BUTTON_X2)) != 0; - - // 处理鼠标滚轮事件(需要在事件循环中处理,这里简化处理) - // 实际滚轮值通过 SDL_MOUSEWHEEL 事件更新 -#endif } void Input::updateGamepad() { if (controller_) { - // 更新按钮状态 for (int i = 0; i < MAX_BUTTONS; ++i) { buttonsDown_[i] = SDL_GameControllerGetButton( controller_, static_cast(i)) != 0; } - // 读取摇杆(归一化到 -1.0 ~ 1.0) leftStickX_ = static_cast(SDL_GameControllerGetAxis( - controller_, SDL_CONTROLLER_AXIS_LEFTX)) / 32767.0f; + controller_, SDL_CONTROLLER_AXIS_LEFTX)) / + 32767.0f; leftStickY_ = static_cast(SDL_GameControllerGetAxis( - controller_, SDL_CONTROLLER_AXIS_LEFTY)) / 32767.0f; + controller_, SDL_CONTROLLER_AXIS_LEFTY)) / + 32767.0f; rightStickX_ = static_cast(SDL_GameControllerGetAxis( - controller_, SDL_CONTROLLER_AXIS_RIGHTX)) / 32767.0f; + controller_, SDL_CONTROLLER_AXIS_RIGHTX)) / + 32767.0f; rightStickY_ = static_cast(SDL_GameControllerGetAxis( - controller_, SDL_CONTROLLER_AXIS_RIGHTY)) / 32767.0f; + controller_, SDL_CONTROLLER_AXIS_RIGHTY)) / + 32767.0f; } else { buttonsDown_.fill(false); leftStickX_ = leftStickY_ = rightStickX_ = rightStickY_ = 0.0f; @@ -124,8 +110,6 @@ void Input::updateGamepad() { } void Input::updateTouch() { -#ifdef PLATFORM_SWITCH - // Switch 原生触摸屏支持 SDL_TouchID touchId = SDL_GetTouchDevice(0); if (touchId != 0) { touchCount_ = SDL_GetNumTouchFingers(touchId); @@ -133,33 +117,12 @@ void Input::updateTouch() { SDL_Finger *finger = SDL_GetTouchFinger(touchId, 0); if (finger) { 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; - SDL_Window* window = SDL_GL_GetCurrentWindow(); + SDL_Window *window = SDL_GL_GetCurrentWindow(); if (window) { SDL_GetWindowSize(window, &windowWidth, &windowHeight); - touchPosition_ = Vec2(finger->x * windowWidth, finger->y * windowHeight); + touchPosition_ = + Vec2(finger->x * windowWidth, finger->y * windowHeight); } else { touchPosition_ = Vec2(finger->x * 1280.0f, finger->y * 720.0f); } @@ -173,113 +136,34 @@ void Input::updateTouch() { touchCount_ = 0; 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 { -#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); if (scancode >= 0 && scancode < MAX_KEYS) { return keysDown_[scancode]; } return false; -#endif } 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); if (scancode >= 0 && scancode < MAX_KEYS) { return keysDown_[scancode] && !prevKeysDown_[scancode]; } return false; -#endif } 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); if (scancode >= 0 && scancode < MAX_KEYS) { return !keysDown_[scancode] && prevKeysDown_[scancode]; } return false; -#endif } // ============================================================================ @@ -316,103 +200,40 @@ bool Input::isMouseDown(MouseButton button) const { int index = static_cast(button); if (index < 0 || index >= 8) 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]; -#endif } bool Input::isMousePressed(MouseButton button) const { int index = static_cast(button); if (index < 0 || index >= 8) 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]; -#endif } bool Input::isMouseReleased(MouseButton button) const { int index = static_cast(button); if (index < 0 || index >= 8) 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]; -#endif } -Vec2 Input::getMousePosition() const { -#ifdef PLATFORM_SWITCH - return touchPosition_; -#else - return mousePosition_; -#endif -} +Vec2 Input::getMousePosition() const { return mousePosition_; } Vec2 Input::getMouseDelta() const { -#ifdef PLATFORM_SWITCH - if (touching_ && prevTouching_) { - return touchPosition_ - prevTouchPosition_; - } - return Vec2::Zero(); -#else return mousePosition_ - prevMousePosition_; -#endif } void Input::setMousePosition(const Vec2 &position) { -#ifndef PLATFORM_SWITCH - SDL_WarpMouseInWindow(SDL_GL_GetCurrentWindow(), - static_cast(position.x), + SDL_WarpMouseInWindow(SDL_GL_GetCurrentWindow(), static_cast(position.x), static_cast(position.y)); -#else - (void)position; -#endif } void Input::setMouseVisible(bool visible) { -#ifndef PLATFORM_SWITCH SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE); -#else - (void)visible; -#endif } void Input::setMouseLocked(bool locked) { -#ifndef PLATFORM_SWITCH SDL_SetRelativeMouseMode(locked ? SDL_TRUE : SDL_FALSE); -#else - (void)locked; -#endif } // ============================================================================ @@ -420,30 +241,66 @@ void Input::setMouseLocked(bool locked) { // ============================================================================ 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) { if (keysDown_[i]) return true; } -#endif return false; } bool Input::isAnyMouseDown() const { -#ifdef PLATFORM_SWITCH - return touching_; -#else for (int i = 0; i < 8; ++i) { if (mouseButtonsDown_[i]) return true; } 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 diff --git a/src/platform/window.cpp b/src/platform/window.cpp index 26049e6..96d6b78 100644 --- a/src/platform/window.cpp +++ b/src/platform/window.cpp @@ -12,12 +12,13 @@ namespace extra2d { Window::Window() : sdlWindow_(nullptr), glContext_(nullptr), currentCursor_(nullptr), width_(1280), height_(720), vsync_(true), shouldClose_(false), - fullscreen_(true), focused_(true), contentScaleX_(1.0f), contentScaleY_(1.0f), - enableDpiScale_(true), userData_(nullptr), eventQueue_(nullptr) { - // 初始化光标数组 - for (int i = 0; i < 9; ++i) { - sdlCursors_[i] = nullptr; - } + fullscreen_(true), focused_(true), contentScaleX_(1.0f), + contentScaleY_(1.0f), enableDpiScale_(true), userData_(nullptr), + eventQueue_(nullptr) { + // 初始化光标数组 + for (int i = 0; i < 9; ++i) { + sdlCursors_[i] = nullptr; + } } Window::~Window() { destroy(); } @@ -55,7 +56,8 @@ bool Window::create(const WindowConfig &config) { bool Window::initSDL(const WindowConfig &config) { // 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()); return false; } @@ -78,7 +80,7 @@ bool Window::initSDL(const WindowConfig &config) { // 创建 SDL2 窗口 Uint32 windowFlags = SDL_WINDOW_OPENGL; - + // 根据配置设置窗口模式 if (config.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, width_, height_, windowFlags); - + if (!sdlWindow_) { E2D_LOG_ERROR("SDL_CreateWindow failed: {}", SDL_GetError()); SDL_Quit(); @@ -129,7 +131,8 @@ bool Window::initSDL(const WindowConfig &config) { } // 加载 OpenGL ES 函数指针 - if (gladLoadGLES2Loader(reinterpret_cast(SDL_GL_GetProcAddress)) == 0) { + if (gladLoadGLES2Loader( + reinterpret_cast(SDL_GL_GetProcAddress)) == 0) { E2D_LOG_ERROR("gladLoadGLES2Loader failed"); SDL_GL_DeleteContext(glContext_); glContext_ = nullptr; @@ -158,7 +161,7 @@ bool Window::initSDL(const WindowConfig &config) { void Window::deinitSDL() { deinitCursors(); - + if (glContext_) { SDL_GL_DeleteContext(glContext_); glContext_ = nullptr; @@ -217,6 +220,28 @@ void Window::pollEvents() { 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; } } diff --git a/src/scene/node.cpp b/src/scene/node.cpp index 1cdcf4c..4d344e2 100644 --- a/src/scene/node.cpp +++ b/src/scene/node.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -334,6 +335,7 @@ void Node::onExit() { } void Node::onUpdate(float dt) { + updateTweens(dt); onUpdateNode(dt); // Update children @@ -460,4 +462,142 @@ void Node::collectRenderCommands(std::vector &commands, } } +Tween& Node::tween() { + auto tw = std::make_shared(this); + tweens_.push_back(tw); + return *tweens_.back(); +} + +Tween& Node::tween(const std::string &name) { + auto tw = std::make_shared(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(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(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(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(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(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(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(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(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(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