refactor: 移除示例游戏和动画系统相关代码

移除 push_box、flappy_bird 等示例游戏代码及相关资源文件
删除动画系统相关实现文件,包括动画解析器、帧渲染器等
清理不再使用的 Action 系统代码
移除 xmake.lua 中不再需要的示例项目配置
This commit is contained in:
ChestnutYueyue 2026-02-14 17:23:58 +08:00
parent 59f01900fb
commit 313a56bf72
136 changed files with 1 additions and 15927 deletions

View File

@ -1,172 +0,0 @@
#pragma once
#include <extra2d/core/types.h>
#include <functional>
namespace extra2d {
class Node;
/**
* @brief
*/
enum class ActionState {
Idle,
Running,
Paused,
Completed
};
/**
* @brief
*
*
* Node
*/
class Action {
public:
using CompletionCallback = std::function<void()>;
Action();
virtual ~Action() = default;
Action(const Action&) = delete;
Action& operator=(const Action&) = delete;
Action(Action&&) = default;
Action& operator=(Action&&) = default;
/**
* @brief
* @return true
*/
virtual bool isDone() const;
/**
* @brief 使
* @param target
*/
virtual void startWithTarget(Node* target);
/**
* @brief
*/
virtual void stop();
/**
* @brief
* @param dt
*/
virtual void step(float dt);
/**
* @brief
* @param time [0, 1]
*/
virtual void update(float time);
/**
* @brief
* @return
*/
virtual Action* clone() const = 0;
/**
* @brief
* @return
*/
virtual Action* reverse() const = 0;
/**
* @brief
*/
void pause();
/**
* @brief
*/
void resume();
/**
* @brief
*/
void restart();
/**
* @brief
* @param callback
*/
void setCompletionCallback(const CompletionCallback& callback) {
completionCallback_ = callback;
}
/**
* @brief
* @return
*/
Node* getTarget() const { return target_; }
/**
* @brief
* @return
*/
Node* getOriginalTarget() const { return originalTarget_; }
/**
* @brief
* @return
*/
ActionState getState() const { return state_; }
/**
* @brief
* @return
*/
int getTag() const { return tag_; }
/**
* @brief
* @param tag
*/
void setTag(int tag) { tag_ = tag; }
/**
* @brief
* @return
*/
unsigned int getFlags() const { return flags_; }
/**
* @brief
* @param flags
*/
void setFlags(unsigned int flags) { flags_ = flags; }
protected:
/**
* @brief
*/
virtual void onStart() {}
/**
* @brief
*/
virtual void onComplete() {
if (completionCallback_) {
completionCallback_();
}
}
/**
* @brief
*/
void setDone() { state_ = ActionState::Completed; }
Node* target_ = nullptr;
Node* originalTarget_ = nullptr;
ActionState state_ = ActionState::Idle;
int tag_ = -1;
unsigned int flags_ = 0;
CompletionCallback completionCallback_;
};
} // namespace extra2d

View File

@ -1,344 +0,0 @@
#pragma once
#include <extra2d/action/action_interval.h>
#include <extra2d/action/ease.h>
namespace extra2d {
/**
* @brief
*
* 使
*/
class ActionEase : public ActionInterval {
public:
virtual ~ActionEase();
/**
* @brief
* @return
*/
ActionInterval* getInnerAction() const { return innerAction_; }
void startWithTarget(Node* target) override;
void stop() override;
void update(float time) override;
ActionInterval* clone() const override = 0;
ActionInterval* reverse() const override = 0;
protected:
ActionEase() = default;
bool initWithAction(ActionInterval* action);
void onUpdate(float progress) override {}
ActionInterval* innerAction_ = nullptr;
};
// ============================================================================
// 指数缓动
// ============================================================================
class EaseExponentialIn : public ActionEase {
public:
static EaseExponentialIn* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
class EaseExponentialOut : public ActionEase {
public:
static EaseExponentialOut* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
class EaseExponentialInOut : public ActionEase {
public:
static EaseExponentialInOut* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
// ============================================================================
// 正弦缓动
// ============================================================================
class EaseSineIn : public ActionEase {
public:
static EaseSineIn* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
class EaseSineOut : public ActionEase {
public:
static EaseSineOut* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
class EaseSineInOut : public ActionEase {
public:
static EaseSineInOut* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
// ============================================================================
// 弹性缓动
// ============================================================================
class EaseElasticIn : public ActionEase {
public:
static EaseElasticIn* create(ActionInterval* action, float period = 0.3f);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
protected:
float period_ = 0.3f;
};
class EaseElasticOut : public ActionEase {
public:
static EaseElasticOut* create(ActionInterval* action, float period = 0.3f);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
protected:
float period_ = 0.3f;
};
class EaseElasticInOut : public ActionEase {
public:
static EaseElasticInOut* create(ActionInterval* action, float period = 0.3f);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
protected:
float period_ = 0.3f;
};
// ============================================================================
// 弹跳缓动
// ============================================================================
class EaseBounceIn : public ActionEase {
public:
static EaseBounceIn* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
class EaseBounceOut : public ActionEase {
public:
static EaseBounceOut* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
class EaseBounceInOut : public ActionEase {
public:
static EaseBounceInOut* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
// ============================================================================
// 回震缓动
// ============================================================================
class EaseBackIn : public ActionEase {
public:
static EaseBackIn* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
class EaseBackOut : public ActionEase {
public:
static EaseBackOut* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
class EaseBackInOut : public ActionEase {
public:
static EaseBackInOut* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
// ============================================================================
// 二次缓动
// ============================================================================
class EaseQuadIn : public ActionEase {
public:
static EaseQuadIn* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
class EaseQuadOut : public ActionEase {
public:
static EaseQuadOut* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
class EaseQuadInOut : public ActionEase {
public:
static EaseQuadInOut* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
// ============================================================================
// 三次缓动
// ============================================================================
class EaseCubicIn : public ActionEase {
public:
static EaseCubicIn* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
class EaseCubicOut : public ActionEase {
public:
static EaseCubicOut* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
class EaseCubicInOut : public ActionEase {
public:
static EaseCubicInOut* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
// ============================================================================
// 四次缓动
// ============================================================================
class EaseQuartIn : public ActionEase {
public:
static EaseQuartIn* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
class EaseQuartOut : public ActionEase {
public:
static EaseQuartOut* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
class EaseQuartInOut : public ActionEase {
public:
static EaseQuartInOut* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
// ============================================================================
// 五次缓动
// ============================================================================
class EaseQuintIn : public ActionEase {
public:
static EaseQuintIn* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
class EaseQuintOut : public ActionEase {
public:
static EaseQuintOut* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
class EaseQuintInOut : public ActionEase {
public:
static EaseQuintInOut* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
// ============================================================================
// 圆形缓动
// ============================================================================
class EaseCircleIn : public ActionEase {
public:
static EaseCircleIn* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
class EaseCircleOut : public ActionEase {
public:
static EaseCircleOut* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
class EaseCircleInOut : public ActionEase {
public:
static EaseCircleInOut* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
};
// ============================================================================
// 自定义缓动
// ============================================================================
/**
* @brief
*/
class EaseCustom : public ActionEase {
public:
static EaseCustom* create(ActionInterval* action, EaseFunction easeFunc);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
void update(float time) override;
protected:
EaseFunction easeFunc_ = nullptr;
};
} // namespace extra2d

View File

@ -1,57 +0,0 @@
#pragma once
#include <extra2d/action/finite_time_action.h>
namespace extra2d {
/**
* @brief
*
*
* FiniteTimeAction 0
*/
class ActionInstant : public FiniteTimeAction {
public:
ActionInstant();
virtual ~ActionInstant() = default;
/**
* @brief
* @return true
*/
bool isDone() const override;
/**
* @brief 使
* @param target
*/
void startWithTarget(Node* target) override;
/**
* @brief
* @param dt
*/
void step(float dt) override;
/**
* @brief
* @return
*/
ActionInstant* clone() const override = 0;
/**
* @brief
* @return
*/
ActionInstant* reverse() const override = 0;
protected:
/**
* @brief
*/
virtual void execute() = 0;
bool done_ = false;
};
} // namespace extra2d

View File

@ -1,169 +0,0 @@
#pragma once
#include <extra2d/action/action_instant.h>
#include <extra2d/core/math_types.h>
#include <functional>
namespace extra2d {
// ============================================================================
// 回调动作
// ============================================================================
/**
* @brief
*/
class CallFunc : public ActionInstant {
public:
using Callback = std::function<void()>;
static CallFunc* create(const Callback& callback);
ActionInstant* clone() const override;
ActionInstant* reverse() const override;
protected:
void execute() override;
Callback callback_;
};
/**
* @brief
*/
class CallFuncN : public ActionInstant {
public:
using Callback = std::function<void(Node*)>;
static CallFuncN* create(const Callback& callback);
ActionInstant* clone() const override;
ActionInstant* reverse() const override;
protected:
void execute() override;
Callback callback_;
};
// ============================================================================
// 位置动作
// ============================================================================
/**
* @brief
*/
class Place : public ActionInstant {
public:
static Place* create(const Vec2& position);
ActionInstant* clone() const override;
ActionInstant* reverse() const override;
protected:
void execute() override;
Vec2 position_;
};
// ============================================================================
// 翻转动作
// ============================================================================
/**
* @brief X轴翻转动作
*/
class FlipX : public ActionInstant {
public:
static FlipX* create(bool flipX);
ActionInstant* clone() const override;
ActionInstant* reverse() const override;
protected:
void execute() override;
bool flipX_ = false;
};
/**
* @brief Y轴翻转动作
*/
class FlipY : public ActionInstant {
public:
static FlipY* create(bool flipY);
ActionInstant* clone() const override;
ActionInstant* reverse() const override;
protected:
void execute() override;
bool flipY_ = false;
};
// ============================================================================
// 可见性动作
// ============================================================================
/**
* @brief
*/
class Show : public ActionInstant {
public:
static Show* create();
ActionInstant* clone() const override;
ActionInstant* reverse() const override;
protected:
void execute() override;
};
/**
* @brief
*/
class Hide : public ActionInstant {
public:
static Hide* create();
ActionInstant* clone() const override;
ActionInstant* reverse() const override;
protected:
void execute() override;
};
/**
* @brief
*/
class ToggleVisibility : public ActionInstant {
public:
static ToggleVisibility* create();
ActionInstant* clone() const override;
ActionInstant* reverse() const override;
protected:
void execute() override;
};
// ============================================================================
// 节点管理动作
// ============================================================================
/**
* @brief
*/
class RemoveSelf : public ActionInstant {
public:
static RemoveSelf* create();
ActionInstant* clone() const override;
ActionInstant* reverse() const override;
protected:
void execute() override;
};
} // namespace extra2d

View File

@ -1,103 +0,0 @@
#pragma once
#include <extra2d/action/finite_time_action.h>
#include <extra2d/action/ease.h>
namespace extra2d {
/**
* @brief
*
*
* FiniteTimeAction
*/
class ActionInterval : public FiniteTimeAction {
public:
ActionInterval() = default;
explicit ActionInterval(float duration);
virtual ~ActionInterval() = default;
/**
* @brief
* @return
*/
float getElapsed() const { return elapsed_; }
/**
* @brief
* @return true
*/
bool isDone() const override;
/**
* @brief 使
* @param target
*/
void startWithTarget(Node* target) override;
/**
* @brief
*/
void stop() override;
/**
* @brief
* @param dt
*/
void step(float dt) override;
/**
* @brief
* @param amp
*/
void setAmplitudeRate(float amp) { amplitudeRate_ = amp; }
/**
* @brief
* @return
*/
float getAmplitudeRate() const { return amplitudeRate_; }
/**
* @brief
* @param easeFunc
*/
void setEaseFunction(EaseFunction easeFunc) { easeFunc_ = easeFunc; }
/**
* @brief
* @return
*/
EaseFunction getEaseFunction() const { return easeFunc_; }
/**
* @brief
* @return
*/
ActionInterval* clone() const override = 0;
/**
* @brief
* @return
*/
ActionInterval* reverse() const override = 0;
protected:
/**
* @brief
*/
virtual void onStart() {}
/**
* @brief
* @param progress [0, 1]
*/
virtual void onUpdate(float progress) = 0;
float elapsed_ = 0.0f;
bool firstTick_ = true;
float amplitudeRate_ = 1.0f;
EaseFunction easeFunc_ = nullptr;
};
} // namespace extra2d

View File

@ -1,469 +0,0 @@
#pragma once
#include <extra2d/action/action_interval.h>
#include <extra2d/core/math_types.h>
#include <extra2d/core/color.h>
#include <functional>
#include <vector>
namespace extra2d {
// ============================================================================
// 移动动作
// ============================================================================
/**
* @brief
*/
class MoveBy : public ActionInterval {
public:
static MoveBy* create(float duration, const Vec2& delta);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
Vec2 delta_;
Vec2 startPosition_;
};
/**
* @brief
*/
class MoveTo : public ActionInterval {
public:
static MoveTo* create(float duration, const Vec2& position);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
Vec2 endPosition_;
Vec2 startPosition_;
Vec2 delta_;
};
// ============================================================================
// 跳跃动作
// ============================================================================
/**
* @brief
*/
class JumpBy : public ActionInterval {
public:
static JumpBy* create(float duration, const Vec2& position, float height, int jumps);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
Vec2 startPosition_;
Vec2 delta_;
float height_ = 0.0f;
int jumps_ = 1;
};
/**
* @brief
*/
class JumpTo : public JumpBy {
public:
static JumpTo* create(float duration, const Vec2& position, float height, int jumps);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
protected:
void onStart() override;
Vec2 endPosition_;
};
// ============================================================================
// 贝塞尔曲线动作
// ============================================================================
/**
* @brief 线
*/
struct BezierConfig {
Vec2 controlPoint1;
Vec2 controlPoint2;
Vec2 endPosition;
};
/**
* @brief 线
*/
class BezierBy : public ActionInterval {
public:
static BezierBy* create(float duration, const BezierConfig& config);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
static float bezierat(float a, float b, float c, float d, float t);
BezierConfig config_;
Vec2 startPosition_;
};
/**
* @brief 线
*/
class BezierTo : public BezierBy {
public:
static BezierTo* create(float duration, const BezierConfig& config);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
protected:
void onStart() override;
BezierConfig originalConfig_;
};
// ============================================================================
// 缩放动作
// ============================================================================
/**
* @brief
*/
class ScaleBy : public ActionInterval {
public:
static ScaleBy* create(float duration, float scale);
static ScaleBy* create(float duration, float scaleX, float scaleY);
static ScaleBy* create(float duration, const Vec2& scale);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
Vec2 deltaScale_;
Vec2 startScale_;
};
/**
* @brief
*/
class ScaleTo : public ActionInterval {
public:
static ScaleTo* create(float duration, float scale);
static ScaleTo* create(float duration, float scaleX, float scaleY);
static ScaleTo* create(float duration, const Vec2& scale);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
Vec2 endScale_;
Vec2 startScale_;
Vec2 delta_;
};
// ============================================================================
// 旋转动作
// ============================================================================
/**
* @brief
*/
class RotateBy : public ActionInterval {
public:
static RotateBy* create(float duration, float deltaAngle);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
float deltaAngle_ = 0.0f;
float startAngle_ = 0.0f;
};
/**
* @brief
*/
class RotateTo : public ActionInterval {
public:
static RotateTo* create(float duration, float angle);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
float endAngle_ = 0.0f;
float startAngle_ = 0.0f;
float deltaAngle_ = 0.0f;
};
// ============================================================================
// 淡入淡出动作
// ============================================================================
/**
* @brief
*/
class FadeIn : public ActionInterval {
public:
static FadeIn* create(float duration);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
float startOpacity_ = 0.0f;
};
/**
* @brief
*/
class FadeOut : public ActionInterval {
public:
static FadeOut* create(float duration);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
float startOpacity_ = 0.0f;
};
/**
* @brief
*/
class FadeTo : public ActionInterval {
public:
static FadeTo* create(float duration, float opacity);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
float endOpacity_ = 0.0f;
float startOpacity_ = 0.0f;
float deltaOpacity_ = 0.0f;
};
// ============================================================================
// 闪烁动作
// ============================================================================
/**
* @brief
*/
class Blink : public ActionInterval {
public:
static Blink* create(float duration, int times);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
int times_ = 1;
int currentTimes_ = 0;
bool originalVisible_ = true;
};
// ============================================================================
// 色调动作
// ============================================================================
/**
* @brief
*/
class TintTo : public ActionInterval {
public:
static TintTo* create(float duration, uint8_t red, uint8_t green, uint8_t blue);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
Color3B startColor_;
Color3B endColor_;
Color3B deltaColor_;
};
/**
* @brief
*/
class TintBy : public ActionInterval {
public:
static TintBy* create(float duration, int16_t deltaRed, int16_t deltaGreen, int16_t deltaBlue);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
Color3B startColor_;
int16_t deltaR_ = 0;
int16_t deltaG_ = 0;
int16_t deltaB_ = 0;
};
// ============================================================================
// 组合动作
// ============================================================================
/**
* @brief
*/
class Sequence : public ActionInterval {
public:
static Sequence* create(ActionInterval* action1, ...);
static Sequence* create(const std::vector<ActionInterval*>& actions);
~Sequence();
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
std::vector<ActionInterval*> actions_;
size_t currentIndex_ = 0;
float split_ = 0.0f;
float last_ = -1.0f;
};
/**
* @brief
*/
class Spawn : public ActionInterval {
public:
static Spawn* create(ActionInterval* action1, ...);
static Spawn* create(const std::vector<ActionInterval*>& actions);
~Spawn();
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
std::vector<ActionInterval*> actions_;
};
/**
* @brief
*/
class Repeat : public ActionInterval {
public:
static Repeat* create(ActionInterval* action, int times);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
bool isDone() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
ActionInterval* innerAction_ = nullptr;
int times_ = 1;
int currentTimes_ = 0;
};
/**
* @brief
*/
class RepeatForever : public ActionInterval {
public:
static RepeatForever* create(ActionInterval* action);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
bool isDone() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
ActionInterval* innerAction_ = nullptr;
};
/**
* @brief
*/
class DelayTime : public ActionInterval {
public:
static DelayTime* create(float duration);
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
protected:
void onUpdate(float progress) override {}
};
/**
* @brief
*/
class ReverseTime : public ActionInterval {
public:
static ReverseTime* create(ActionInterval* action);
~ReverseTime();
ActionInterval* clone() const override;
ActionInterval* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
ActionInterval* innerAction_ = nullptr;
};
} // namespace extra2d

View File

@ -1,130 +0,0 @@
#pragma once
#include <extra2d/action/action.h>
#include <unordered_map>
#include <vector>
#include <functional>
namespace extra2d {
/**
* @brief
*
*
*
*/
class ActionManager {
public:
/**
* @brief
* @return ActionManager
*/
static ActionManager* getInstance();
/**
* @brief
*/
static void destroyInstance();
/**
* @brief
* @param action
* @param target
* @param paused
*/
void addAction(Action* action, Node* target, bool paused = false);
/**
* @brief
* @param action
*/
void removeAction(Action* action);
/**
* @brief
* @param tag
* @param target
*/
void removeActionByTag(int tag, Node* target);
/**
* @brief
* @param flags
* @param target
*/
void removeActionsByFlags(unsigned int flags, Node* target);
/**
* @brief
* @param target
*/
void removeAllActionsFromTarget(Node* target);
/**
* @brief
*/
void removeAllActions();
/**
* @brief
* @param tag
* @param target
* @return nullptr
*/
Action* getActionByTag(int tag, Node* target);
/**
* @brief
* @param target
* @return
*/
size_t getActionCount(Node* target) const;
/**
* @brief
* @param target
*/
void pauseTarget(Node* target);
/**
* @brief
* @param target
*/
void resumeTarget(Node* target);
/**
* @brief
* @param target
* @return true
*/
bool isPaused(Node* target) const;
/**
* @brief
* @param dt
*/
void update(float dt);
private:
ActionManager();
~ActionManager();
struct ActionElement {
std::vector<Action*> actions;
Node* target = nullptr;
bool paused = false;
int actionIndex = 0;
Action* currentAction = nullptr;
bool currentActionSalvaged = false;
};
using ActionMap = std::unordered_map<Node*, ActionElement>;
void removeActionAt(size_t index, ActionElement& element);
void deleteAction(Action* action);
ActionMap targets_;
static ActionManager* instance_;
};
} // namespace extra2d

View File

@ -1,166 +0,0 @@
#pragma once
#include <extra2d/action/action.h>
#include <extra2d/action/action_interval.h>
#include <extra2d/core/math_types.h>
namespace extra2d {
/**
* @brief
*
*
*
*/
class Speed : public Action {
public:
/**
* @brief
* @param action
* @param speed 1.0
* @return
*/
static Speed* create(ActionInterval* action, float speed);
~Speed();
/**
* @brief
* @return
*/
float getSpeed() const { return speed_; }
/**
* @brief
* @param speed
*/
void setSpeed(float speed) { speed_ = speed; }
/**
* @brief
* @return
*/
ActionInterval* getInnerAction() const { return innerAction_; }
void startWithTarget(Node* target) override;
void stop() override;
void step(float dt) override;
bool isDone() const override;
Action* clone() const override;
Action* reverse() const override;
protected:
Speed() = default;
ActionInterval* innerAction_ = nullptr;
float speed_ = 1.0f;
};
/**
* @brief
*
* 使
*
*/
class Follow : public Action {
public:
/**
* @brief
* @param followedNode
* @return
*/
static Follow* create(Node* followedNode);
/**
* @brief
* @param followedNode
* @param boundary
* @return
*/
static Follow* create(Node* followedNode, const Rect& boundary);
~Follow();
/**
* @brief
* @return
*/
Node* getFollowedNode() const { return followedNode_; }
/**
* @brief
* @return true
*/
bool isBoundarySet() const { return boundarySet_; }
void startWithTarget(Node* target) override;
void stop() override;
void step(float dt) override;
bool isDone() const override;
Action* clone() const override;
Action* reverse() const override;
protected:
Follow() = default;
Node* followedNode_ = nullptr;
Rect boundary_;
bool boundarySet_ = false;
Vec2 halfScreenSize_;
Vec2 fullScreenSize_;
Vec2 leftBoundary_;
Vec2 rightBoundary_;
Vec2 topBoundary_;
Vec2 bottomBoundary_;
};
/**
* @brief
*
*
*/
class TargetedAction : public Action {
public:
/**
* @brief
* @param target
* @param action
* @return
*/
static TargetedAction* create(Node* target, FiniteTimeAction* action);
~TargetedAction();
/**
* @brief
* @return
*/
Node* getTargetNode() const { return targetNode_; }
/**
* @brief
* @param target
*/
void setTargetNode(Node* target) { targetNode_ = target; }
/**
* @brief
* @return
*/
FiniteTimeAction* getAction() const { return innerAction_; }
void startWithTarget(Node* target) override;
void stop() override;
void step(float dt) override;
bool isDone() const override;
Action* clone() const override;
Action* reverse() const override;
protected:
TargetedAction() = default;
Node* targetNode_ = nullptr;
FiniteTimeAction* innerAction_ = nullptr;
};
} // namespace extra2d

View File

@ -1,101 +0,0 @@
#pragma once
namespace extra2d {
/**
* @brief
*/
using EaseFunction = float (*)(float);
// ============================================================================
// 线性缓动
// ============================================================================
/**
* @brief 线
* @param t [0, 1]
* @return
*/
float easeLinear(float t);
// ============================================================================
// 二次缓动 (Quad)
// ============================================================================
float easeInQuad(float t);
float easeOutQuad(float t);
float easeInOutQuad(float t);
// ============================================================================
// 三次缓动 (Cubic)
// ============================================================================
float easeInCubic(float t);
float easeOutCubic(float t);
float easeInOutCubic(float t);
// ============================================================================
// 四次缓动 (Quart)
// ============================================================================
float easeInQuart(float t);
float easeOutQuart(float t);
float easeInOutQuart(float t);
// ============================================================================
// 五次缓动 (Quint)
// ============================================================================
float easeInQuint(float t);
float easeOutQuint(float t);
float easeInOutQuint(float t);
// ============================================================================
// 正弦缓动 (Sine)
// ============================================================================
float easeInSine(float t);
float easeOutSine(float t);
float easeInOutSine(float t);
// ============================================================================
// 指数缓动 (Exponential)
// ============================================================================
float easeInExpo(float t);
float easeOutExpo(float t);
float easeInOutExpo(float t);
// ============================================================================
// 圆形缓动 (Circular)
// ============================================================================
float easeInCirc(float t);
float easeOutCirc(float t);
float easeInOutCirc(float t);
// ============================================================================
// 回震缓动 (Back)
// ============================================================================
float easeInBack(float t);
float easeOutBack(float t);
float easeInOutBack(float t);
// ============================================================================
// 弹性缓动 (Elastic)
// ============================================================================
float easeInElastic(float t);
float easeOutElastic(float t);
float easeInOutElastic(float t);
// ============================================================================
// 弹跳缓动 (Bounce)
// ============================================================================
float easeInBounce(float t);
float easeOutBounce(float t);
float easeInOutBounce(float t);
} // namespace extra2d

View File

@ -1,47 +0,0 @@
#pragma once
#include <extra2d/action/action.h>
namespace extra2d {
/**
* @brief
*
*
* Action
*/
class FiniteTimeAction : public Action {
public:
FiniteTimeAction() = default;
explicit FiniteTimeAction(float duration);
virtual ~FiniteTimeAction() = default;
/**
* @brief
* @return
*/
float getDuration() const { return duration_; }
/**
* @brief
* @param duration
*/
void setDuration(float duration) { duration_ = duration; }
/**
* @brief
* @return
*/
FiniteTimeAction* clone() const override = 0;
/**
* @brief
* @return
*/
FiniteTimeAction* reverse() const override = 0;
protected:
float duration_ = 0.0f;
};
} // namespace extra2d

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,73 +0,0 @@
#pragma once
#include <extra2d/core/math_types.h>
#include <extra2d/core/types.h>
#include <extra2d/graphics/texture.h>
#include <string>
namespace extra2d {
// ============================================================================
// SpriteFrame - 精灵帧(纹理 + 区域 + 偏移的中间抽象)
// 借鉴 Cocos2d-x SpriteFrame解耦纹理物理存储与逻辑帧
// 一个纹理图集可包含多个 SpriteFrame减少纹理切换提升渲染性能
// ============================================================================
class SpriteFrame {
public:
SpriteFrame() = default;
SpriteFrame(Ptr<Texture> texture, const Rect &rect)
: texture_(std::move(texture)), rect_(rect), originalSize_(rect.size) {}
SpriteFrame(Ptr<Texture> texture, const Rect &rect, const Vec2 &offset,
const Size &originalSize)
: texture_(std::move(texture)), rect_(rect), offset_(offset),
originalSize_(originalSize) {}
// ------ 静态创建 ------
static Ptr<SpriteFrame> create(Ptr<Texture> texture, const Rect &rect) {
return makePtr<SpriteFrame>(std::move(texture), rect);
}
static Ptr<SpriteFrame> create(Ptr<Texture> texture, const Rect &rect,
const Vec2 &offset, const Size &originalSize) {
return makePtr<SpriteFrame>(std::move(texture), rect, offset, originalSize);
}
// ------ 纹理信息 ------
void setTexture(Ptr<Texture> texture) { texture_ = std::move(texture); }
Ptr<Texture> getTexture() const { return texture_; }
// ------ 矩形区域(在纹理图集中的位置)------
void setRect(const Rect &rect) { rect_ = rect; }
const Rect &getRect() const { return rect_; }
// ------ 偏移(图集打包时的裁剪偏移)------
void setOffset(const Vec2 &offset) { offset_ = offset; }
const Vec2 &getOffset() const { return offset_; }
// ------ 原始尺寸(裁剪前的完整尺寸)------
void setOriginalSize(const Size &size) { originalSize_ = size; }
const Size &getOriginalSize() const { return originalSize_; }
// ------ 旋转标志图集工具可能旋转90度------
void setRotated(bool rotated) { rotated_ = rotated; }
bool isRotated() const { return rotated_; }
// ------ 名称(用于缓存索引)------
void setName(const std::string &name) { name_ = name; }
const std::string &getName() const { return name_; }
// ------ 有效性检查 ------
bool isValid() const { return texture_ != nullptr; }
private:
Ptr<Texture> texture_;
Rect rect_;
Vec2 offset_;
Size originalSize_;
bool rotated_ = false;
std::string name_;
};
} // namespace extra2d

View File

@ -1,201 +0,0 @@
#pragma once
#include <extra2d/animation/sprite_frame.h>
#include <extra2d/graphics/texture.h>
#include <extra2d/resource/resource_manager.h>
#include <extra2d/utils/logger.h>
#include <mutex>
#include <string>
#include <unordered_map>
namespace extra2d {
// ============================================================================
// SpriteFrameCache - 精灵帧全局缓存(借鉴 Cocos SpriteFrameCache
// 全局单例管理所有精灵帧,避免重复创建,支持图集自动切割
// ============================================================================
class SpriteFrameCache {
public:
static SpriteFrameCache &getInstance() {
static SpriteFrameCache instance;
return instance;
}
// ------ 添加帧 ------
/// 添加单个精灵帧
void addSpriteFrame(Ptr<SpriteFrame> frame, const std::string &name) {
std::lock_guard<std::mutex> lock(mutex_);
frames_[name] = std::move(frame);
}
/// 从纹理和矩形区域创建并添加帧
void addSpriteFrameFromTexture(Ptr<Texture> texture, const Rect &rect,
const std::string &name) {
auto frame = SpriteFrame::create(std::move(texture), rect);
frame->setName(name);
addSpriteFrame(std::move(frame), name);
}
/// 从纹理图集批量切割添加(等宽等高网格)
void addSpriteFramesFromGrid(const std::string &texturePath, int frameWidth,
int frameHeight, int frameCount = -1,
int spacing = 0, int margin = 0) {
auto texture = loadTextureFromFile(texturePath);
if (!texture)
return;
addSpriteFramesFromGrid(texture, texturePath, frameWidth, frameHeight,
frameCount, spacing, margin);
}
/// 从纹理对象批量切割添加(等宽等高网格,无需走 TexturePool
void addSpriteFramesFromGrid(Ptr<Texture> texture,
const std::string &keyPrefix, int frameWidth,
int frameHeight, int frameCount = -1,
int spacing = 0, int margin = 0) {
if (!texture)
return;
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;
std::lock_guard<std::mutex> lock(mutex_);
for (int i = 0; i < total; ++i) {
int col = i % cols;
int row = i / cols;
if (row >= rows)
break;
Rect rect(static_cast<float>(margin + col * (frameWidth + spacing)),
static_cast<float>(margin + row * (frameHeight + spacing)),
static_cast<float>(frameWidth),
static_cast<float>(frameHeight));
std::string name = keyPrefix + "#" + std::to_string(i);
auto frame = SpriteFrame::create(texture, rect);
frame->setName(name);
frames_[name] = std::move(frame);
}
}
// ------ 获取帧 ------
/// 按名称获取
Ptr<SpriteFrame> getSpriteFrame(const std::string &name) const {
std::lock_guard<std::mutex> lock(mutex_);
auto it = frames_.find(name);
if (it != frames_.end())
return it->second;
return nullptr;
}
/// 通过路径+索引获取或创建ANI 格式的定位方式)
Ptr<SpriteFrame> getOrCreateFromFile(const std::string &texturePath,
int index = 0) {
std::string key = texturePath + "#" + std::to_string(index);
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = frames_.find(key);
if (it != frames_.end())
return it->second;
}
// 缓存未命中,加载纹理并创建 SpriteFrame
auto texture = loadTextureFromFile(texturePath);
if (!texture)
return nullptr;
// 默认整张纹理作为一帧index=0或用整张纹理
Rect rect(0.0f, 0.0f, static_cast<float>(texture->getWidth()),
static_cast<float>(texture->getHeight()));
auto frame = SpriteFrame::create(texture, rect);
frame->setName(key);
std::lock_guard<std::mutex> lock(mutex_);
frames_[key] = frame;
return frame;
}
// ------ 缓存管理 ------
bool has(const std::string &name) const {
std::lock_guard<std::mutex> lock(mutex_);
return frames_.find(name) != frames_.end();
}
void removeSpriteFrame(const std::string &name) {
std::lock_guard<std::mutex> lock(mutex_);
frames_.erase(name);
}
/// 移除未被外部引用的精灵帧use_count == 1 表示仅缓存自身持有)
void removeUnusedSpriteFrames() {
std::lock_guard<std::mutex> lock(mutex_);
for (auto it = frames_.begin(); it != frames_.end();) {
if (it->second.use_count() == 1) {
it = frames_.erase(it);
} else {
++it;
}
}
}
void clear() {
std::lock_guard<std::mutex> lock(mutex_);
frames_.clear();
}
size_t count() const {
std::lock_guard<std::mutex> lock(mutex_);
return frames_.size();
}
private:
SpriteFrameCache() = default;
~SpriteFrameCache() = default;
SpriteFrameCache(const SpriteFrameCache &) = delete;
SpriteFrameCache &operator=(const SpriteFrameCache &) = delete;
/**
* @brief 使 ResourceManager LRU
* @param filepath
* @return nullptr
*/
Ptr<Texture> loadTextureFromFile(const std::string &filepath) {
// 使用 ResourceManager 的纹理缓存机制
// 这样可以享受 LRU 缓存、自动清理和缓存统计等功能
auto &resources = ResourceManager::getInstance();
// 先检查缓存中是否已有该纹理
auto texture = resources.getTexture(filepath);
if (texture) {
E2D_TRACE("SpriteFrameCache: 使用缓存纹理: {}", filepath);
return texture;
}
// 缓存未命中,通过 ResourceManager 加载
texture = resources.loadTexture(filepath);
if (!texture) {
E2D_ERROR("SpriteFrameCache: 加载纹理失败: {}", filepath);
return nullptr;
}
E2D_TRACE("SpriteFrameCache: 加载新纹理: {}", filepath);
return texture;
}
mutable std::mutex mutex_;
std::unordered_map<std::string, Ptr<SpriteFrame>> frames_;
};
// 便捷宏
#define E2D_SPRITE_FRAME_CACHE() ::extra2d::SpriteFrameCache::getInstance()
} // namespace extra2d

View File

@ -1,324 +0,0 @@
#pragma once
#include <extra2d/core/color.h>
#include <extra2d/core/types.h>
#include <extra2d/effects/particle_system.h>
#include <extra2d/effects/post_process.h>
#include <extra2d/graphics/shader_system.h>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>
namespace extra2d {
// ============================================================================
// 自定义特效类型
// ============================================================================
enum class CustomEffectType {
Particle, // 粒子特效
PostProcess, // 后处理特效
Shader, // Shader特效
Combined // 组合特效
};
// ============================================================================
// 自定义特效配置
// ============================================================================
struct CustomEffectConfig {
std::string name; // 特效名称
CustomEffectType type; // 特效类型
std::string description; // 描述
// 粒子特效配置
EmitterConfig emitterConfig;
// 后处理特效配置
std::string shaderVertPath; // 顶点着色器路径
std::string shaderFragPath; // 片段着色器路径
std::unordered_map<std::string, float> shaderParams; // Shader参数
// 通用配置
float duration; // 持续时间(-1表示无限)
bool loop; // 是否循环
float delay; // 延迟启动时间
};
// ============================================================================
// 自定义特效基类
// ============================================================================
class CustomEffect {
public:
explicit CustomEffect(const CustomEffectConfig &config);
virtual ~CustomEffect() = default;
// ------------------------------------------------------------------------
// 生命周期
// ------------------------------------------------------------------------
virtual bool init();
virtual void update(float dt);
virtual void render(RenderBackend &renderer);
virtual void shutdown();
// ------------------------------------------------------------------------
// 控制
// ------------------------------------------------------------------------
void play();
void pause();
void stop();
void reset();
bool isPlaying() const { return playing_; }
bool isFinished() const { return finished_; }
float getElapsedTime() const { return elapsedTime_; }
// ------------------------------------------------------------------------
// 配置
// ------------------------------------------------------------------------
const std::string &getName() const { return config_.name; }
const CustomEffectConfig &getConfig() const { return config_; }
void setPosition(const Vec2 &pos) { position_ = pos; }
void setRotation(float rot) { rotation_ = rot; }
void setScale(float scale) { scale_ = scale; }
Vec2 getPosition() const { return position_; }
float getRotation() const { return rotation_; }
float getScale() const { return scale_; }
protected:
CustomEffectConfig config_;
Vec2 position_ = Vec2::Zero();
float rotation_ = 0.0f;
float scale_ = 1.0f;
bool playing_ = false;
bool paused_ = false;
bool finished_ = false;
float elapsedTime_ = 0.0f;
float delayTimer_ = 0.0f;
};
// ============================================================================
// 自定义粒子特效
// ============================================================================
class CustomParticleEffect : public CustomEffect {
public:
explicit CustomParticleEffect(const CustomEffectConfig &config);
bool init() override;
void update(float dt) override;
void render(RenderBackend &renderer) override;
void shutdown() override;
void play();
void stop();
Ptr<ParticleEmitter> getEmitter() { return emitter_; }
private:
Ptr<ParticleSystem> particleSystem_;
Ptr<ParticleEmitter> emitter_;
};
// ============================================================================
// 自定义后处理特效
// ============================================================================
class CustomPostProcessEffect : public CustomEffect, public PostProcessEffect {
public:
explicit CustomPostProcessEffect(const CustomEffectConfig &config);
bool init() override;
void update(float dt) override;
void shutdown() override;
void onShaderBind(GLShader &shader) override;
void setParam(const std::string &name, float value);
float getParam(const std::string &name) const;
private:
std::unordered_map<std::string, float> runtimeParams_;
};
// ============================================================================
// 自定义特效工厂
// ============================================================================
class CustomEffectFactory {
public:
using EffectCreator =
std::function<Ptr<CustomEffect>(const CustomEffectConfig &)>;
static CustomEffectFactory &getInstance();
// 注册自定义特效创建器
void registerEffect(const std::string &typeName, EffectCreator creator);
// 创建特效
Ptr<CustomEffect> create(const std::string &typeName,
const CustomEffectConfig &config);
// 检查是否已注册
bool isRegistered(const std::string &typeName) const;
// 获取所有已注册的类型
std::vector<std::string> getRegisteredTypes() const;
private:
CustomEffectFactory() = default;
~CustomEffectFactory() = default;
CustomEffectFactory(const CustomEffectFactory &) = delete;
CustomEffectFactory &operator=(const CustomEffectFactory &) = delete;
std::unordered_map<std::string, EffectCreator> creators_;
};
// ============================================================================
// 自定义特效管理器
// ============================================================================
class CustomEffectManager {
public:
static CustomEffectManager &getInstance();
// ------------------------------------------------------------------------
// 初始化和关闭
// ------------------------------------------------------------------------
bool init();
void shutdown();
// ------------------------------------------------------------------------
// 特效管理
// ------------------------------------------------------------------------
/**
* @brief JSON和文本格式
* JSON格式优先退
*/
bool loadFromFile(const std::string &filepath);
/**
* @brief
* EMISSION 100
*/
bool loadFromTextFile(const std::string &filepath);
/**
* @brief
* @param useJson true=JSON格式, false=
*/
bool saveToFile(const std::string &name, const std::string &filepath,
bool useJson = true);
/**
* @brief JSON文件
*/
bool saveAllToFile(const std::string &filepath);
/**
* @brief
*/
void registerConfig(const std::string &name,
const CustomEffectConfig &config);
/**
* @brief
*/
CustomEffectConfig *getConfig(const std::string &name);
/**
* @brief
*/
void removeConfig(const std::string &name);
/**
* @brief
*/
std::vector<std::string> getConfigNames() const;
// ------------------------------------------------------------------------
// 特效实例管理
// ------------------------------------------------------------------------
/**
* @brief
*/
Ptr<CustomEffect> createEffect(const std::string &name);
/**
* @brief
*/
Ptr<CustomEffect> createEffectFromConfig(const CustomEffectConfig &config);
/**
* @brief
*/
void destroyEffect(Ptr<CustomEffect> effect);
/**
* @brief
*/
void update(float dt);
/**
* @brief
*/
void render(RenderBackend &renderer);
/**
* @brief
*/
void stopAll();
// ------------------------------------------------------------------------
// 便捷方法
// ------------------------------------------------------------------------
/**
* @brief
*/
Ptr<CustomEffect> play(const std::string &name, const Vec2 &position);
/**
* @brief
*/
void playOneShot(const std::string &name, const Vec2 &position);
private:
CustomEffectManager() = default;
~CustomEffectManager() = default;
CustomEffectManager(const CustomEffectManager &) = delete;
CustomEffectManager &operator=(const CustomEffectManager &) = delete;
std::unordered_map<std::string, CustomEffectConfig> configs_;
std::vector<Ptr<CustomEffect>> activeEffects_;
};
// ============================================================================
// 便捷宏
// ============================================================================
#define E2D_CUSTOM_EFFECT_MANAGER() \
::extra2d::CustomEffectManager::getInstance()
#define E2D_CUSTOM_EFFECT_FACTORY() \
::extra2d::CustomEffectFactory::getInstance()
// ============================================================================
// 预设特效快速创建
// ============================================================================
class EffectBuilder {
public:
// 粒子特效
static CustomEffectConfig Particle(const std::string &name);
static CustomEffectConfig Fire(const std::string &name);
static CustomEffectConfig Smoke(const std::string &name);
static CustomEffectConfig Explosion(const std::string &name);
static CustomEffectConfig Magic(const std::string &name);
static CustomEffectConfig Sparkle(const std::string &name);
// 后处理特效
static CustomEffectConfig Bloom(const std::string &name);
static CustomEffectConfig Blur(const std::string &name);
static CustomEffectConfig Vignette(const std::string &name);
static CustomEffectConfig ColorGrading(const std::string &name);
};
} // namespace extra2d

View File

@ -1,300 +0,0 @@
#pragma once
#include <extra2d/core/color.h>
#include <extra2d/core/types.h>
#include <extra2d/graphics/texture.h>
#include <extra2d/scene/node.h>
#include <functional>
#include <random>
#include <vector>
namespace extra2d {
// ============================================================================
// 快速随机数生成器 - 使用 xorshift 算法,比 std::mt19937 更快
// ============================================================================
class FastRNG {
public:
explicit FastRNG(uint32_t seed = 0) : state_(seed ? seed : 0x853c49e67) {}
float nextFloat() {
return static_cast<float>(next()) / static_cast<float>(UINT32_MAX);
}
float nextFloat(float min, float max) {
return min + (max - min) * nextFloat();
}
private:
uint32_t state_;
uint32_t next() {
uint32_t x = state_;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
state_ = x;
return x;
}
};
// ============================================================================
// 粒子数据
// ============================================================================
struct Particle {
Vec2 position;
Vec2 velocity;
Vec2 acceleration;
float rotation;
float angularVelocity;
float size;
float sizeDelta;
Color color;
Color colorDelta;
float life;
float maxLife;
bool active;
Particle()
: position(Vec2::Zero()), velocity(Vec2::Zero()),
acceleration(Vec2::Zero()), rotation(0.0f), angularVelocity(0.0f),
size(1.0f), sizeDelta(0.0f), color(Colors::White),
colorDelta(Colors::Transparent), life(0.0f), maxLife(1.0f),
active(false) {}
};
// ============================================================================
// 发射器配置
// ============================================================================
struct EmitterConfig {
// 发射速率
float emissionRate = 100.0f; // 每秒发射粒子数
float emissionDuration = -1.0f; // 发射持续时间(-1表示无限
// 粒子生命周期
float minLife = 1.0f;
float maxLife = 2.0f;
// 粒子大小
float minStartSize = 10.0f;
float maxStartSize = 20.0f;
float minEndSize = 0.0f;
float maxEndSize = 5.0f;
// 粒子速度
Vec2 minVelocity = Vec2(-50.0f, -50.0f);
Vec2 maxVelocity = Vec2(50.0f, 50.0f);
// 粒子加速度
Vec2 acceleration = Vec2(0.0f, -100.0f); // 重力
// 粒子旋转
float minRotation = 0.0f;
float maxRotation = 360.0f;
float minAngularVelocity = -90.0f;
float maxAngularVelocity = 90.0f;
// 颜色
Color startColor = Colors::White;
Color endColor = Colors::Transparent;
// 发射形状
enum class Shape {
Point, // 点发射
Circle, // 圆形区域
Rectangle, // 矩形区域
Cone // 锥形
};
Shape shape = Shape::Point;
float shapeRadius = 50.0f; // 圆形/锥形半径
Vec2 shapeSize = Vec2(100.0f, 100.0f); // 矩形大小
float coneAngle = 45.0f; // 锥形角度
// 纹理
Ptr<Texture> texture = nullptr;
// 混合模式
BlendMode blendMode = BlendMode::Additive;
};
// ============================================================================
// 粒子发射器
// ============================================================================
class ParticleEmitter {
public:
ParticleEmitter();
~ParticleEmitter() = default;
// 形状生成函数(公有,用于查找表)
Vec2 randomPointShape();
Vec2 randomCircleShape();
Vec2 randomRectangleShape();
Vec2 randomConeShape();
// ------------------------------------------------------------------------
// 初始化和关闭
// ------------------------------------------------------------------------
bool init(size_t maxParticles);
void shutdown();
// ------------------------------------------------------------------------
// 配置
// ------------------------------------------------------------------------
void setConfig(const EmitterConfig &config) { config_ = config; }
const EmitterConfig &getConfig() const { return config_; }
// 链式配置API
ParticleEmitter &withEmissionRate(float rate) {
config_.emissionRate = rate;
return *this;
}
ParticleEmitter &withLife(float minLife, float maxLife) {
config_.minLife = minLife;
config_.maxLife = maxLife;
return *this;
}
ParticleEmitter &withSize(float minStart, float maxStart, float minEnd = 0.0f,
float maxEnd = 0.0f) {
config_.minStartSize = minStart;
config_.maxStartSize = maxStart;
config_.minEndSize = minEnd;
config_.maxEndSize = maxEnd;
return *this;
}
ParticleEmitter &withVelocity(const Vec2 &minVel, const Vec2 &maxVel) {
config_.minVelocity = minVel;
config_.maxVelocity = maxVel;
return *this;
}
ParticleEmitter &withAcceleration(const Vec2 &accel) {
config_.acceleration = accel;
return *this;
}
ParticleEmitter &withColor(const Color &start, const Color &end) {
config_.startColor = start;
config_.endColor = end;
return *this;
}
ParticleEmitter &withTexture(Ptr<Texture> texture) {
config_.texture = texture;
return *this;
}
ParticleEmitter &withBlendMode(BlendMode mode) {
config_.blendMode = mode;
return *this;
}
// ------------------------------------------------------------------------
// 发射控制
// ------------------------------------------------------------------------
void start();
void stop();
void burst(int count); // 爆发发射
void reset();
bool isEmitting() const { return emitting_; }
// ------------------------------------------------------------------------
// 更新和渲染
// ------------------------------------------------------------------------
void update(float dt);
void render(RenderBackend &renderer);
// ------------------------------------------------------------------------
// 状态查询
// ------------------------------------------------------------------------
size_t getActiveParticleCount() const { return activeCount_; }
size_t getMaxParticles() const { return particles_.size(); }
bool isActive() const { return activeCount_ > 0 || emitting_; }
// ------------------------------------------------------------------------
// 变换
// ------------------------------------------------------------------------
void setPosition(const Vec2 &pos) { position_ = pos; }
void setRotation(float rot) { rotation_ = rot; }
Vec2 getPosition() const { return position_; }
float getRotation() const { return rotation_; }
private:
EmitterConfig config_;
std::vector<Particle> particles_;
size_t activeCount_ = 0;
Vec2 position_ = Vec2::Zero();
float rotation_ = 0.0f;
bool emitting_ = false;
float emissionTimer_ = 0.0f;
float emissionTime_ = 0.0f;
FastRNG rng_; // 使用快速 RNG 替代 std::mt19937
void emitParticle();
float randomFloat(float min, float max);
Vec2 randomPointInShape();
Vec2 randomVelocity();
};
// ============================================================================
// 粒子系统 - 管理多个发射器
// ============================================================================
class ParticleSystem : public Node {
public:
ParticleSystem();
~ParticleSystem() override = default;
// ------------------------------------------------------------------------
// 静态创建方法
// ------------------------------------------------------------------------
static Ptr<ParticleSystem> create();
// ------------------------------------------------------------------------
// 发射器管理
// ------------------------------------------------------------------------
Ptr<ParticleEmitter> addEmitter(const EmitterConfig &config = {});
void removeEmitter(Ptr<ParticleEmitter> emitter);
void removeAllEmitters();
size_t getEmitterCount() const { return emitters_.size(); }
// ------------------------------------------------------------------------
// 全局控制
// ------------------------------------------------------------------------
void startAll();
void stopAll();
void resetAll();
// ------------------------------------------------------------------------
// 预设
// ------------------------------------------------------------------------
static EmitterConfig PresetFire();
static EmitterConfig PresetSmoke();
static EmitterConfig PresetExplosion();
static EmitterConfig PresetSparkle();
static EmitterConfig PresetRain();
static EmitterConfig PresetSnow();
// ------------------------------------------------------------------------
// 重写Node方法
// ------------------------------------------------------------------------
void onUpdate(float dt) override;
void onDraw(RenderBackend &renderer) override;
private:
std::vector<Ptr<ParticleEmitter>> emitters_;
};
// ============================================================================
// 粒子预设(便捷类)
// ============================================================================
class ParticlePreset {
public:
static EmitterConfig Fire();
static EmitterConfig Smoke();
static EmitterConfig Explosion();
static EmitterConfig Sparkle();
static EmitterConfig Rain();
static EmitterConfig Snow();
static EmitterConfig Magic();
static EmitterConfig Bubbles();
};
} // namespace extra2d

View File

@ -1,228 +0,0 @@
#pragma once
#include <extra2d/core/color.h>
#include <extra2d/core/types.h>
#include <extra2d/graphics/opengl/gl_shader.h>
#include <extra2d/graphics/texture.h>
#include <functional>
#include <string>
#include <vector>
namespace extra2d {
// ============================================================================
// 前向声明
// ============================================================================
class RenderTarget;
class RenderBackend;
// ============================================================================
// 后处理效果基类
// ============================================================================
class PostProcessEffect {
public:
PostProcessEffect(const std::string &name);
virtual ~PostProcessEffect() = default;
// ------------------------------------------------------------------------
// 生命周期
// ------------------------------------------------------------------------
/**
* @brief
*/
virtual bool init();
/**
* @brief
*/
virtual void shutdown();
// ------------------------------------------------------------------------
// 渲染
// ------------------------------------------------------------------------
/**
* @brief
* @param source
* @param target
* @param renderer
*/
virtual void apply(const Texture &source, RenderTarget &target,
RenderBackend &renderer);
/**
* @brief Shader参数
*/
virtual void onShaderBind(GLShader &shader) {}
// ------------------------------------------------------------------------
// 状态
// ------------------------------------------------------------------------
const std::string &getName() const { return name_; }
bool isEnabled() const { return enabled_; }
void setEnabled(bool enabled) { enabled_ = enabled; }
bool isValid() const { return valid_; }
// ------------------------------------------------------------------------
// 链式API
// ------------------------------------------------------------------------
PostProcessEffect &withEnabled(bool enabled) {
enabled_ = enabled;
return *this;
}
protected:
std::string name_;
bool enabled_ = true;
bool valid_ = false;
Ptr<GLShader> shader_;
/**
* @brief Shader
*/
bool loadShader(const std::string &vertSource, const std::string &fragSource);
bool loadShaderFromFile(const std::string &vertPath,
const std::string &fragPath);
/**
* @brief
*/
void renderFullscreenQuad();
private:
static GLuint quadVao_;
static GLuint quadVbo_;
static bool quadInitialized_;
void initQuad();
void destroyQuad();
};
// ============================================================================
// 后处理栈 - 管理多个后处理效果
// ============================================================================
class PostProcessStack {
public:
PostProcessStack();
~PostProcessStack();
// ------------------------------------------------------------------------
// 初始化和关闭
// ------------------------------------------------------------------------
bool init(int width, int height);
void shutdown();
// ------------------------------------------------------------------------
// 效果管理
// ------------------------------------------------------------------------
/**
* @brief
*/
void addEffect(Ptr<PostProcessEffect> effect);
/**
* @brief
*/
void insertEffect(size_t index, Ptr<PostProcessEffect> effect);
/**
* @brief
*/
void removeEffect(const std::string &name);
void removeEffect(size_t index);
/**
* @brief
*/
Ptr<PostProcessEffect> getEffect(const std::string &name);
Ptr<PostProcessEffect> getEffect(size_t index);
/**
* @brief
*/
void clearEffects();
/**
* @brief
*/
size_t getEffectCount() const { return effects_.size(); }
// ------------------------------------------------------------------------
// 渲染
// ------------------------------------------------------------------------
/**
* @brief
*/
void beginCapture();
/**
* @brief
*/
void endCapture(RenderBackend &renderer);
/**
* @brief
*/
void process(const Texture &source, RenderTarget &target,
RenderBackend &renderer);
// ------------------------------------------------------------------------
// 配置
// ------------------------------------------------------------------------
void resize(int width, int height);
bool isValid() const { return valid_; }
// ------------------------------------------------------------------------
// 便捷方法 - 添加内置效果
// ------------------------------------------------------------------------
PostProcessStack &addBloom(float intensity = 1.0f, float threshold = 0.8f);
PostProcessStack &addBlur(float radius = 2.0f);
PostProcessStack &addColorGrading(const Color &tint);
PostProcessStack &addVignette(float intensity = 0.5f);
PostProcessStack &addChromaticAberration(float amount = 1.0f);
private:
std::vector<Ptr<PostProcessEffect>> effects_;
Ptr<RenderTarget> renderTargetA_;
Ptr<RenderTarget> renderTargetB_;
int width_ = 0;
int height_ = 0;
bool valid_ = false;
bool capturing_ = false;
};
// ============================================================================
// 全局后处理管理
// ============================================================================
class PostProcessManager {
public:
static PostProcessManager &getInstance();
void init(int width, int height);
void shutdown();
PostProcessStack &getMainStack() { return mainStack_; }
void resize(int width, int height);
void beginFrame();
void endFrame(RenderBackend &renderer);
private:
PostProcessManager() = default;
~PostProcessManager() = default;
PostProcessManager(const PostProcessManager &) = delete;
PostProcessManager &operator=(const PostProcessManager &) = delete;
PostProcessStack mainStack_;
bool initialized_ = false;
};
// ============================================================================
// 便捷宏
// ============================================================================
#define E2D_POST_PROCESS() ::extra2d::PostProcessManager::getInstance()
} // namespace extra2d

View File

@ -36,24 +36,6 @@
#include <extra2d/scene/transition_flip_scene.h>
#include <extra2d/scene/transition_box_scene.h>
// Animation
#include <extra2d/animation/sprite_frame.h>
#include <extra2d/animation/sprite_frame_cache.h>
#include <extra2d/animation/frame_property.h>
#include <extra2d/animation/animation_frame.h>
#include <extra2d/animation/animation_clip.h>
#include <extra2d/animation/animation_controller.h>
#include <extra2d/animation/animation_cache.h>
#include <extra2d/animation/interpolation_engine.h>
#include <extra2d/animation/animated_sprite.h>
#include <extra2d/animation/frame_renderer.h>
#include <extra2d/animation/animation_event.h>
#include <extra2d/animation/animation_node.h>
#include <extra2d/animation/composite_animation.h>
#include <extra2d/animation/ani_parser.h>
#include <extra2d/animation/ani_binary_parser.h>
#include <extra2d/animation/als_parser.h>
// UI
#include <extra2d/ui/widget.h>
#include <extra2d/ui/button.h>
@ -64,18 +46,6 @@
#include <extra2d/ui/radio_button.h>
#include <extra2d/ui/slider.h>
// Action
#include <extra2d/action/action.h>
#include <extra2d/action/finite_time_action.h>
#include <extra2d/action/action_interval.h>
#include <extra2d/action/action_instant.h>
#include <extra2d/action/action_interval_actions.h>
#include <extra2d/action/action_instant_actions.h>
#include <extra2d/action/action_ease.h>
#include <extra2d/action/action_special.h>
#include <extra2d/action/action_manager.h>
#include <extra2d/action/ease.h>
// Event
#include <extra2d/event/event.h>
#include <extra2d/event/event_queue.h>
@ -101,14 +71,9 @@
#include <extra2d/spatial/spatial_hash.h>
#include <extra2d/spatial/spatial_manager.h>
// Effects
#include <extra2d/effects/post_process.h>
#include <extra2d/effects/particle_system.h>
#include <extra2d/effects/custom_effect_manager.h>
// Application
#include <extra2d/app/application.h>
#ifdef __SWITCH__
#include <switch.h>
#endif
#endif

View File

@ -14,7 +14,6 @@ namespace extra2d {
// 前向声明
class Scene;
class Action;
class RenderBackend;
struct RenderCommand;
@ -155,58 +154,6 @@ public:
// 更新空间索引(手动调用,通常在边界框变化后)
void updateSpatialIndex();
// ------------------------------------------------------------------------
// 动作系统
// ------------------------------------------------------------------------
/**
* @brief
* @param action
* @return
*/
Action* runAction(Action* action);
/**
* @brief
*/
void stopAllActions();
/**
* @brief
* @param action
*/
void stopAction(Action* action);
/**
* @brief
* @param tag
*/
void stopActionByTag(int tag);
/**
* @brief
* @param flags
*/
void stopActionsByFlags(unsigned int flags);
/**
* @brief
* @param tag
* @return nullptr
*/
Action* getActionByTag(int tag);
/**
* @brief
* @return
*/
size_t getActionCount() const;
/**
* @brief
* @return true
*/
bool isRunningActions() const;
// ------------------------------------------------------------------------
// 事件系统
// ------------------------------------------------------------------------

View File

@ -1,49 +0,0 @@
#include "extra2d/action/action.h"
#include "extra2d/scene/node.h"
namespace extra2d {
Action::Action() : tag_(-1), flags_(0) {}
bool Action::isDone() const {
return state_ == ActionState::Completed;
}
void Action::startWithTarget(Node* target) {
target_ = target;
originalTarget_ = target;
state_ = ActionState::Running;
onStart();
}
void Action::stop() {
target_ = nullptr;
state_ = ActionState::Completed;
}
void Action::step(float dt) {
(void)dt;
}
void Action::update(float time) {
(void)time;
}
void Action::pause() {
if (state_ == ActionState::Running) {
state_ = ActionState::Paused;
}
}
void Action::resume() {
if (state_ == ActionState::Paused) {
state_ = ActionState::Running;
}
}
void Action::restart() {
state_ = ActionState::Running;
onStart();
}
} // namespace extra2d

View File

@ -1,683 +0,0 @@
#include "extra2d/action/action_ease.h"
#include <cmath>
#ifndef M_PI
#define M_PI 3.14159265358979323846f
#endif
namespace extra2d {
// ============================================================================
// ActionEase 基类
// ============================================================================
ActionEase::~ActionEase() {
delete innerAction_;
}
bool ActionEase::initWithAction(ActionInterval* action) {
if (!action) {
return false;
}
innerAction_ = action;
duration_ = action->getDuration();
return true;
}
void ActionEase::startWithTarget(Node* target) {
ActionInterval::startWithTarget(target);
innerAction_->startWithTarget(target);
}
void ActionEase::stop() {
innerAction_->stop();
ActionInterval::stop();
}
void ActionEase::update(float time) {
innerAction_->update(time);
}
// ============================================================================
// 指数缓动
// ============================================================================
EaseExponentialIn* EaseExponentialIn::create(ActionInterval* action) {
auto* ease = new EaseExponentialIn();
ease->initWithAction(action);
return ease;
}
void EaseExponentialIn::update(float time) {
innerAction_->update(easeInExpo(time));
}
ActionInterval* EaseExponentialIn::clone() const {
return EaseExponentialIn::create(innerAction_->clone());
}
ActionInterval* EaseExponentialIn::reverse() const {
return EaseExponentialOut::create(innerAction_->reverse());
}
EaseExponentialOut* EaseExponentialOut::create(ActionInterval* action) {
auto* ease = new EaseExponentialOut();
ease->initWithAction(action);
return ease;
}
void EaseExponentialOut::update(float time) {
innerAction_->update(easeOutExpo(time));
}
ActionInterval* EaseExponentialOut::clone() const {
return EaseExponentialOut::create(innerAction_->clone());
}
ActionInterval* EaseExponentialOut::reverse() const {
return EaseExponentialIn::create(innerAction_->reverse());
}
EaseExponentialInOut* EaseExponentialInOut::create(ActionInterval* action) {
auto* ease = new EaseExponentialInOut();
ease->initWithAction(action);
return ease;
}
void EaseExponentialInOut::update(float time) {
innerAction_->update(easeInOutExpo(time));
}
ActionInterval* EaseExponentialInOut::clone() const {
return EaseExponentialInOut::create(innerAction_->clone());
}
ActionInterval* EaseExponentialInOut::reverse() const {
return EaseExponentialInOut::create(innerAction_->reverse());
}
// ============================================================================
// 正弦缓动
// ============================================================================
EaseSineIn* EaseSineIn::create(ActionInterval* action) {
auto* ease = new EaseSineIn();
ease->initWithAction(action);
return ease;
}
void EaseSineIn::update(float time) {
innerAction_->update(easeInSine(time));
}
ActionInterval* EaseSineIn::clone() const {
return EaseSineIn::create(innerAction_->clone());
}
ActionInterval* EaseSineIn::reverse() const {
return EaseSineOut::create(innerAction_->reverse());
}
EaseSineOut* EaseSineOut::create(ActionInterval* action) {
auto* ease = new EaseSineOut();
ease->initWithAction(action);
return ease;
}
void EaseSineOut::update(float time) {
innerAction_->update(easeOutSine(time));
}
ActionInterval* EaseSineOut::clone() const {
return EaseSineOut::create(innerAction_->clone());
}
ActionInterval* EaseSineOut::reverse() const {
return EaseSineIn::create(innerAction_->reverse());
}
EaseSineInOut* EaseSineInOut::create(ActionInterval* action) {
auto* ease = new EaseSineInOut();
ease->initWithAction(action);
return ease;
}
void EaseSineInOut::update(float time) {
innerAction_->update(easeInOutSine(time));
}
ActionInterval* EaseSineInOut::clone() const {
return EaseSineInOut::create(innerAction_->clone());
}
ActionInterval* EaseSineInOut::reverse() const {
return EaseSineInOut::create(innerAction_->reverse());
}
// ============================================================================
// 弹性缓动
// ============================================================================
EaseElasticIn* EaseElasticIn::create(ActionInterval* action, float period) {
auto* ease = new EaseElasticIn();
ease->initWithAction(action);
ease->period_ = period;
return ease;
}
void EaseElasticIn::update(float time) {
float newT = 0.0f;
if (time == 0.0f || time == 1.0f) {
newT = time;
} else {
float s = period_ / 4.0f;
time = time - 1.0f;
newT = -std::pow(2.0f, 10.0f * time) * std::sin((time - s) * M_PI * 2.0f / period_);
}
innerAction_->update(newT);
}
ActionInterval* EaseElasticIn::clone() const {
return EaseElasticIn::create(innerAction_->clone(), period_);
}
ActionInterval* EaseElasticIn::reverse() const {
return EaseElasticOut::create(innerAction_->reverse(), period_);
}
EaseElasticOut* EaseElasticOut::create(ActionInterval* action, float period) {
auto* ease = new EaseElasticOut();
ease->initWithAction(action);
ease->period_ = period;
return ease;
}
void EaseElasticOut::update(float time) {
float newT = 0.0f;
if (time == 0.0f || time == 1.0f) {
newT = time;
} else {
float s = period_ / 4.0f;
newT = std::pow(2.0f, -10.0f * time) * std::sin((time - s) * M_PI * 2.0f / period_) + 1.0f;
}
innerAction_->update(newT);
}
ActionInterval* EaseElasticOut::clone() const {
return EaseElasticOut::create(innerAction_->clone(), period_);
}
ActionInterval* EaseElasticOut::reverse() const {
return EaseElasticIn::create(innerAction_->reverse(), period_);
}
EaseElasticInOut* EaseElasticInOut::create(ActionInterval* action, float period) {
auto* ease = new EaseElasticInOut();
ease->initWithAction(action);
ease->period_ = period;
return ease;
}
void EaseElasticInOut::update(float time) {
float newT = 0.0f;
if (time == 0.0f || time == 1.0f) {
newT = time;
} else {
time = time * 2.0f;
if (period_ == 0.0f) {
period_ = 0.3f * 1.5f;
}
float s = period_ / 4.0f;
if (time < 1.0f) {
time -= 1.0f;
newT = -0.5f * std::pow(2.0f, 10.0f * time) * std::sin((time - s) * M_PI * 2.0f / period_);
} else {
time -= 1.0f;
newT = std::pow(2.0f, -10.0f * time) * std::sin((time - s) * M_PI * 2.0f / period_) * 0.5f + 1.0f;
}
}
innerAction_->update(newT);
}
ActionInterval* EaseElasticInOut::clone() const {
return EaseElasticInOut::create(innerAction_->clone(), period_);
}
ActionInterval* EaseElasticInOut::reverse() const {
return EaseElasticInOut::create(innerAction_->reverse(), period_);
}
// ============================================================================
// 弹跳缓动
// ============================================================================
EaseBounceIn* EaseBounceIn::create(ActionInterval* action) {
auto* ease = new EaseBounceIn();
ease->initWithAction(action);
return ease;
}
void EaseBounceIn::update(float time) {
innerAction_->update(easeInBounce(time));
}
ActionInterval* EaseBounceIn::clone() const {
return EaseBounceIn::create(innerAction_->clone());
}
ActionInterval* EaseBounceIn::reverse() const {
return EaseBounceOut::create(innerAction_->reverse());
}
EaseBounceOut* EaseBounceOut::create(ActionInterval* action) {
auto* ease = new EaseBounceOut();
ease->initWithAction(action);
return ease;
}
void EaseBounceOut::update(float time) {
innerAction_->update(easeOutBounce(time));
}
ActionInterval* EaseBounceOut::clone() const {
return EaseBounceOut::create(innerAction_->clone());
}
ActionInterval* EaseBounceOut::reverse() const {
return EaseBounceIn::create(innerAction_->reverse());
}
EaseBounceInOut* EaseBounceInOut::create(ActionInterval* action) {
auto* ease = new EaseBounceInOut();
ease->initWithAction(action);
return ease;
}
void EaseBounceInOut::update(float time) {
innerAction_->update(easeInOutBounce(time));
}
ActionInterval* EaseBounceInOut::clone() const {
return EaseBounceInOut::create(innerAction_->clone());
}
ActionInterval* EaseBounceInOut::reverse() const {
return EaseBounceInOut::create(innerAction_->reverse());
}
// ============================================================================
// 回震缓动
// ============================================================================
EaseBackIn* EaseBackIn::create(ActionInterval* action) {
auto* ease = new EaseBackIn();
ease->initWithAction(action);
return ease;
}
void EaseBackIn::update(float time) {
innerAction_->update(easeInBack(time));
}
ActionInterval* EaseBackIn::clone() const {
return EaseBackIn::create(innerAction_->clone());
}
ActionInterval* EaseBackIn::reverse() const {
return EaseBackOut::create(innerAction_->reverse());
}
EaseBackOut* EaseBackOut::create(ActionInterval* action) {
auto* ease = new EaseBackOut();
ease->initWithAction(action);
return ease;
}
void EaseBackOut::update(float time) {
innerAction_->update(easeOutBack(time));
}
ActionInterval* EaseBackOut::clone() const {
return EaseBackOut::create(innerAction_->clone());
}
ActionInterval* EaseBackOut::reverse() const {
return EaseBackIn::create(innerAction_->reverse());
}
EaseBackInOut* EaseBackInOut::create(ActionInterval* action) {
auto* ease = new EaseBackInOut();
ease->initWithAction(action);
return ease;
}
void EaseBackInOut::update(float time) {
innerAction_->update(easeInOutBack(time));
}
ActionInterval* EaseBackInOut::clone() const {
return EaseBackInOut::create(innerAction_->clone());
}
ActionInterval* EaseBackInOut::reverse() const {
return EaseBackInOut::create(innerAction_->reverse());
}
// ============================================================================
// 二次缓动
// ============================================================================
EaseQuadIn* EaseQuadIn::create(ActionInterval* action) {
auto* ease = new EaseQuadIn();
ease->initWithAction(action);
return ease;
}
void EaseQuadIn::update(float time) {
innerAction_->update(easeInQuad(time));
}
ActionInterval* EaseQuadIn::clone() const {
return EaseQuadIn::create(innerAction_->clone());
}
ActionInterval* EaseQuadIn::reverse() const {
return EaseQuadOut::create(innerAction_->reverse());
}
EaseQuadOut* EaseQuadOut::create(ActionInterval* action) {
auto* ease = new EaseQuadOut();
ease->initWithAction(action);
return ease;
}
void EaseQuadOut::update(float time) {
innerAction_->update(easeOutQuad(time));
}
ActionInterval* EaseQuadOut::clone() const {
return EaseQuadOut::create(innerAction_->clone());
}
ActionInterval* EaseQuadOut::reverse() const {
return EaseQuadIn::create(innerAction_->reverse());
}
EaseQuadInOut* EaseQuadInOut::create(ActionInterval* action) {
auto* ease = new EaseQuadInOut();
ease->initWithAction(action);
return ease;
}
void EaseQuadInOut::update(float time) {
innerAction_->update(easeInOutQuad(time));
}
ActionInterval* EaseQuadInOut::clone() const {
return EaseQuadInOut::create(innerAction_->clone());
}
ActionInterval* EaseQuadInOut::reverse() const {
return EaseQuadInOut::create(innerAction_->reverse());
}
// ============================================================================
// 三次缓动
// ============================================================================
EaseCubicIn* EaseCubicIn::create(ActionInterval* action) {
auto* ease = new EaseCubicIn();
ease->initWithAction(action);
return ease;
}
void EaseCubicIn::update(float time) {
innerAction_->update(easeInCubic(time));
}
ActionInterval* EaseCubicIn::clone() const {
return EaseCubicIn::create(innerAction_->clone());
}
ActionInterval* EaseCubicIn::reverse() const {
return EaseCubicOut::create(innerAction_->reverse());
}
EaseCubicOut* EaseCubicOut::create(ActionInterval* action) {
auto* ease = new EaseCubicOut();
ease->initWithAction(action);
return ease;
}
void EaseCubicOut::update(float time) {
innerAction_->update(easeOutCubic(time));
}
ActionInterval* EaseCubicOut::clone() const {
return EaseCubicOut::create(innerAction_->clone());
}
ActionInterval* EaseCubicOut::reverse() const {
return EaseCubicIn::create(innerAction_->reverse());
}
EaseCubicInOut* EaseCubicInOut::create(ActionInterval* action) {
auto* ease = new EaseCubicInOut();
ease->initWithAction(action);
return ease;
}
void EaseCubicInOut::update(float time) {
innerAction_->update(easeInOutCubic(time));
}
ActionInterval* EaseCubicInOut::clone() const {
return EaseCubicInOut::create(innerAction_->clone());
}
ActionInterval* EaseCubicInOut::reverse() const {
return EaseCubicInOut::create(innerAction_->reverse());
}
// ============================================================================
// 四次缓动
// ============================================================================
EaseQuartIn* EaseQuartIn::create(ActionInterval* action) {
auto* ease = new EaseQuartIn();
ease->initWithAction(action);
return ease;
}
void EaseQuartIn::update(float time) {
innerAction_->update(easeInQuart(time));
}
ActionInterval* EaseQuartIn::clone() const {
return EaseQuartIn::create(innerAction_->clone());
}
ActionInterval* EaseQuartIn::reverse() const {
return EaseQuartOut::create(innerAction_->reverse());
}
EaseQuartOut* EaseQuartOut::create(ActionInterval* action) {
auto* ease = new EaseQuartOut();
ease->initWithAction(action);
return ease;
}
void EaseQuartOut::update(float time) {
innerAction_->update(easeOutQuart(time));
}
ActionInterval* EaseQuartOut::clone() const {
return EaseQuartOut::create(innerAction_->clone());
}
ActionInterval* EaseQuartOut::reverse() const {
return EaseQuartIn::create(innerAction_->reverse());
}
EaseQuartInOut* EaseQuartInOut::create(ActionInterval* action) {
auto* ease = new EaseQuartInOut();
ease->initWithAction(action);
return ease;
}
void EaseQuartInOut::update(float time) {
innerAction_->update(easeInOutQuart(time));
}
ActionInterval* EaseQuartInOut::clone() const {
return EaseQuartInOut::create(innerAction_->clone());
}
ActionInterval* EaseQuartInOut::reverse() const {
return EaseQuartInOut::create(innerAction_->reverse());
}
// ============================================================================
// 五次缓动
// ============================================================================
EaseQuintIn* EaseQuintIn::create(ActionInterval* action) {
auto* ease = new EaseQuintIn();
ease->initWithAction(action);
return ease;
}
void EaseQuintIn::update(float time) {
innerAction_->update(easeInQuint(time));
}
ActionInterval* EaseQuintIn::clone() const {
return EaseQuintIn::create(innerAction_->clone());
}
ActionInterval* EaseQuintIn::reverse() const {
return EaseQuintOut::create(innerAction_->reverse());
}
EaseQuintOut* EaseQuintOut::create(ActionInterval* action) {
auto* ease = new EaseQuintOut();
ease->initWithAction(action);
return ease;
}
void EaseQuintOut::update(float time) {
innerAction_->update(easeOutQuint(time));
}
ActionInterval* EaseQuintOut::clone() const {
return EaseQuintOut::create(innerAction_->clone());
}
ActionInterval* EaseQuintOut::reverse() const {
return EaseQuintIn::create(innerAction_->reverse());
}
EaseQuintInOut* EaseQuintInOut::create(ActionInterval* action) {
auto* ease = new EaseQuintInOut();
ease->initWithAction(action);
return ease;
}
void EaseQuintInOut::update(float time) {
innerAction_->update(easeInOutQuint(time));
}
ActionInterval* EaseQuintInOut::clone() const {
return EaseQuintInOut::create(innerAction_->clone());
}
ActionInterval* EaseQuintInOut::reverse() const {
return EaseQuintInOut::create(innerAction_->reverse());
}
// ============================================================================
// 圆形缓动
// ============================================================================
EaseCircleIn* EaseCircleIn::create(ActionInterval* action) {
auto* ease = new EaseCircleIn();
ease->initWithAction(action);
return ease;
}
void EaseCircleIn::update(float time) {
innerAction_->update(easeInCirc(time));
}
ActionInterval* EaseCircleIn::clone() const {
return EaseCircleIn::create(innerAction_->clone());
}
ActionInterval* EaseCircleIn::reverse() const {
return EaseCircleOut::create(innerAction_->reverse());
}
EaseCircleOut* EaseCircleOut::create(ActionInterval* action) {
auto* ease = new EaseCircleOut();
ease->initWithAction(action);
return ease;
}
void EaseCircleOut::update(float time) {
innerAction_->update(easeOutCirc(time));
}
ActionInterval* EaseCircleOut::clone() const {
return EaseCircleOut::create(innerAction_->clone());
}
ActionInterval* EaseCircleOut::reverse() const {
return EaseCircleIn::create(innerAction_->reverse());
}
EaseCircleInOut* EaseCircleInOut::create(ActionInterval* action) {
auto* ease = new EaseCircleInOut();
ease->initWithAction(action);
return ease;
}
void EaseCircleInOut::update(float time) {
innerAction_->update(easeInOutCirc(time));
}
ActionInterval* EaseCircleInOut::clone() const {
return EaseCircleInOut::create(innerAction_->clone());
}
ActionInterval* EaseCircleInOut::reverse() const {
return EaseCircleInOut::create(innerAction_->reverse());
}
// ============================================================================
// 自定义缓动
// ============================================================================
EaseCustom* EaseCustom::create(ActionInterval* action, EaseFunction easeFunc) {
auto* ease = new EaseCustom();
ease->initWithAction(action);
ease->easeFunc_ = easeFunc;
return ease;
}
void EaseCustom::update(float time) {
if (easeFunc_) {
innerAction_->update(easeFunc_(time));
} else {
innerAction_->update(time);
}
}
ActionInterval* EaseCustom::clone() const {
return EaseCustom::create(innerAction_->clone(), easeFunc_);
}
ActionInterval* EaseCustom::reverse() const {
return EaseCustom::create(innerAction_->reverse(), easeFunc_);
}
} // namespace extra2d

View File

@ -1,30 +0,0 @@
#include "extra2d/action/action_instant.h"
#include "extra2d/scene/node.h"
namespace extra2d {
ActionInstant::ActionInstant() {
duration_ = 0.0f;
}
bool ActionInstant::isDone() const {
return done_;
}
void ActionInstant::startWithTarget(Node* target) {
FiniteTimeAction::startWithTarget(target);
done_ = false;
}
void ActionInstant::step(float dt) {
(void)dt;
if (state_ != ActionState::Running) {
return;
}
execute();
done_ = true;
state_ = ActionState::Completed;
onComplete();
}
} // namespace extra2d

View File

@ -1,202 +0,0 @@
#include "extra2d/action/action_instant_actions.h"
#include "extra2d/scene/node.h"
namespace extra2d {
// ============================================================================
// 回调动作
// ============================================================================
CallFunc* CallFunc::create(const Callback& callback) {
auto* action = new CallFunc();
action->callback_ = callback;
return action;
}
void CallFunc::execute() {
if (callback_) {
callback_();
}
}
ActionInstant* CallFunc::clone() const {
return CallFunc::create(callback_);
}
ActionInstant* CallFunc::reverse() const {
return CallFunc::create(callback_);
}
// CallFuncN
CallFuncN* CallFuncN::create(const Callback& callback) {
auto* action = new CallFuncN();
action->callback_ = callback;
return action;
}
void CallFuncN::execute() {
if (callback_ && target_) {
callback_(target_);
}
}
ActionInstant* CallFuncN::clone() const {
return CallFuncN::create(callback_);
}
ActionInstant* CallFuncN::reverse() const {
return CallFuncN::create(callback_);
}
// ============================================================================
// 位置动作
// ============================================================================
Place* Place::create(const Vec2& position) {
auto* action = new Place();
action->position_ = position;
return action;
}
void Place::execute() {
if (target_) {
target_->setPosition(position_);
}
}
ActionInstant* Place::clone() const {
return Place::create(position_);
}
ActionInstant* Place::reverse() const {
return Place::create(position_);
}
// ============================================================================
// 翻转动作
// ============================================================================
FlipX* FlipX::create(bool flipX) {
auto* action = new FlipX();
action->flipX_ = flipX;
return action;
}
void FlipX::execute() {
if (target_) {
target_->setFlipX(flipX_);
}
}
ActionInstant* FlipX::clone() const {
return FlipX::create(flipX_);
}
ActionInstant* FlipX::reverse() const {
return FlipX::create(!flipX_);
}
// FlipY
FlipY* FlipY::create(bool flipY) {
auto* action = new FlipY();
action->flipY_ = flipY;
return action;
}
void FlipY::execute() {
if (target_) {
target_->setFlipY(flipY_);
}
}
ActionInstant* FlipY::clone() const {
return FlipY::create(flipY_);
}
ActionInstant* FlipY::reverse() const {
return FlipY::create(!flipY_);
}
// ============================================================================
// 可见性动作
// ============================================================================
Show* Show::create() {
return new Show();
}
void Show::execute() {
if (target_) {
target_->setVisible(true);
}
}
ActionInstant* Show::clone() const {
return Show::create();
}
ActionInstant* Show::reverse() const {
return Hide::create();
}
// Hide
Hide* Hide::create() {
return new Hide();
}
void Hide::execute() {
if (target_) {
target_->setVisible(false);
}
}
ActionInstant* Hide::clone() const {
return Hide::create();
}
ActionInstant* Hide::reverse() const {
return Show::create();
}
// ToggleVisibility
ToggleVisibility* ToggleVisibility::create() {
return new ToggleVisibility();
}
void ToggleVisibility::execute() {
if (target_) {
target_->setVisible(!target_->isVisible());
}
}
ActionInstant* ToggleVisibility::clone() const {
return ToggleVisibility::create();
}
ActionInstant* ToggleVisibility::reverse() const {
return ToggleVisibility::create();
}
// ============================================================================
// 节点管理动作
// ============================================================================
RemoveSelf* RemoveSelf::create() {
return new RemoveSelf();
}
void RemoveSelf::execute() {
if (target_) {
target_->removeFromParent();
}
}
ActionInstant* RemoveSelf::clone() const {
return RemoveSelf::create();
}
ActionInstant* RemoveSelf::reverse() const {
return RemoveSelf::create();
}
} // namespace extra2d

View File

@ -1,56 +0,0 @@
#include "extra2d/action/action_interval.h"
#include "extra2d/scene/node.h"
namespace extra2d {
ActionInterval::ActionInterval(float duration)
: FiniteTimeAction(duration) {
}
bool ActionInterval::isDone() const {
return elapsed_ >= duration_;
}
void ActionInterval::startWithTarget(Node* target) {
FiniteTimeAction::startWithTarget(target);
elapsed_ = 0.0f;
firstTick_ = true;
onStart();
}
void ActionInterval::stop() {
FiniteTimeAction::stop();
}
void ActionInterval::step(float dt) {
if (state_ != ActionState::Running) {
return;
}
if (firstTick_) {
firstTick_ = false;
elapsed_ = 0.0f;
} else {
elapsed_ += dt;
}
float progress = 0.0f;
if (duration_ > 0.0f) {
progress = std::min(1.0f, elapsed_ / duration_);
} else {
progress = 1.0f;
}
if (easeFunc_) {
progress = easeFunc_(progress);
}
onUpdate(progress);
if (progress >= 1.0f) {
state_ = ActionState::Completed;
onComplete();
}
}
} // namespace extra2d

View File

@ -1,787 +0,0 @@
#include "extra2d/action/action_interval_actions.h"
#include "extra2d/scene/node.h"
#include <algorithm>
#include <cstdarg>
namespace extra2d {
// ============================================================================
// 移动动作
// ============================================================================
MoveBy* MoveBy::create(float duration, const Vec2& delta) {
auto* action = new MoveBy();
action->duration_ = duration;
action->delta_ = delta;
return action;
}
void MoveBy::onStart() {
startPosition_ = target_->getPosition();
}
void MoveBy::onUpdate(float progress) {
Vec2 newPos = startPosition_ + delta_ * progress;
target_->setPosition(newPos);
}
ActionInterval* MoveBy::clone() const {
return MoveBy::create(duration_, delta_);
}
ActionInterval* MoveBy::reverse() const {
return MoveBy::create(duration_, -delta_);
}
// MoveTo
MoveTo* MoveTo::create(float duration, const Vec2& position) {
auto* action = new MoveTo();
action->duration_ = duration;
action->endPosition_ = position;
return action;
}
void MoveTo::onStart() {
startPosition_ = target_->getPosition();
delta_ = endPosition_ - startPosition_;
}
void MoveTo::onUpdate(float progress) {
Vec2 newPos = startPosition_ + delta_ * progress;
target_->setPosition(newPos);
}
ActionInterval* MoveTo::clone() const {
return MoveTo::create(duration_, endPosition_);
}
ActionInterval* MoveTo::reverse() const {
return MoveTo::create(duration_, startPosition_);
}
// ============================================================================
// 跳跃动作
// ============================================================================
JumpBy* JumpBy::create(float duration, const Vec2& position, float height, int jumps) {
auto* action = new JumpBy();
action->duration_ = duration;
action->delta_ = position;
action->height_ = height;
action->jumps_ = jumps;
return action;
}
void JumpBy::onStart() {
startPosition_ = target_->getPosition();
}
void JumpBy::onUpdate(float progress) {
float frac = (progress * jumps_) - static_cast<int>(progress * jumps_);
float y = height_ * 4.0f * frac * (1.0f - frac);
y += delta_.y * progress;
float x = delta_.x * progress;
target_->setPosition(startPosition_ + Vec2(x, y));
}
ActionInterval* JumpBy::clone() const {
return JumpBy::create(duration_, delta_, height_, jumps_);
}
ActionInterval* JumpBy::reverse() const {
return JumpBy::create(duration_, -delta_, height_, jumps_);
}
// JumpTo
JumpTo* JumpTo::create(float duration, const Vec2& position, float height, int jumps) {
auto* action = new JumpTo();
action->duration_ = duration;
action->endPosition_ = position;
action->height_ = height;
action->jumps_ = jumps;
return action;
}
void JumpTo::onStart() {
JumpBy::onStart();
delta_ = endPosition_ - startPosition_;
}
ActionInterval* JumpTo::clone() const {
return JumpTo::create(duration_, endPosition_, height_, jumps_);
}
ActionInterval* JumpTo::reverse() const {
return JumpTo::create(duration_, startPosition_, height_, jumps_);
}
// ============================================================================
// 贝塞尔曲线动作
// ============================================================================
BezierBy* BezierBy::create(float duration, const BezierConfig& config) {
auto* action = new BezierBy();
action->duration_ = duration;
action->config_ = config;
return action;
}
void BezierBy::onStart() {
startPosition_ = target_->getPosition();
}
void BezierBy::onUpdate(float progress) {
float xa = startPosition_.x;
float xb = config_.controlPoint1.x;
float xc = config_.controlPoint2.x;
float xd = config_.endPosition.x;
float ya = startPosition_.y;
float yb = config_.controlPoint1.y;
float yc = config_.controlPoint2.y;
float yd = config_.endPosition.y;
float x = bezierat(xa, xb, xc, xd, progress);
float y = bezierat(ya, yb, yc, yd, progress);
target_->setPosition(Vec2(x, y));
}
ActionInterval* BezierBy::clone() const {
return BezierBy::create(duration_, config_);
}
ActionInterval* BezierBy::reverse() const {
BezierConfig rev;
rev.controlPoint1 = config_.controlPoint2 + config_.endPosition;
rev.controlPoint2 = config_.controlPoint1 + config_.endPosition;
rev.endPosition = config_.endPosition;
return BezierBy::create(duration_, rev);
}
float BezierBy::bezierat(float a, float b, float c, float d, float t) {
return (powf(1 - t, 3) * a +
3.0f * t * powf(1 - t, 2) * b +
3.0f * t * t * (1 - t) * c +
t * t * t * d);
}
// BezierTo
BezierTo* BezierTo::create(float duration, const BezierConfig& config) {
auto* action = new BezierTo();
action->duration_ = duration;
action->originalConfig_ = config;
return action;
}
void BezierTo::onStart() {
BezierBy::onStart();
config_.controlPoint1 = originalConfig_.controlPoint1 - startPosition_;
config_.controlPoint2 = originalConfig_.controlPoint2 - startPosition_;
config_.endPosition = originalConfig_.endPosition - startPosition_;
}
ActionInterval* BezierTo::clone() const {
return BezierTo::create(duration_, originalConfig_);
}
ActionInterval* BezierTo::reverse() const {
BezierConfig rev;
rev.controlPoint1 = originalConfig_.controlPoint2;
rev.controlPoint2 = originalConfig_.controlPoint1;
rev.endPosition = startPosition_;
return BezierTo::create(duration_, rev);
}
// ============================================================================
// 缩放动作
// ============================================================================
ScaleBy* ScaleBy::create(float duration, float scale) {
return create(duration, scale, scale);
}
ScaleBy* ScaleBy::create(float duration, float scaleX, float scaleY) {
auto* action = new ScaleBy();
action->duration_ = duration;
action->deltaScale_ = Vec2(scaleX - 1.0f, scaleY - 1.0f);
return action;
}
ScaleBy* ScaleBy::create(float duration, const Vec2& scale) {
return create(duration, scale.x, scale.y);
}
void ScaleBy::onStart() {
startScale_ = target_->getScale();
}
void ScaleBy::onUpdate(float progress) {
Vec2 newScale = startScale_ + deltaScale_ * progress;
target_->setScale(newScale);
}
ActionInterval* ScaleBy::clone() const {
return ScaleBy::create(duration_, Vec2(startScale_.x + deltaScale_.x, startScale_.y + deltaScale_.y));
}
ActionInterval* ScaleBy::reverse() const {
return ScaleBy::create(duration_, Vec2(startScale_.x - deltaScale_.x, startScale_.y - deltaScale_.y));
}
// ScaleTo
ScaleTo* ScaleTo::create(float duration, float scale) {
return create(duration, scale, scale);
}
ScaleTo* ScaleTo::create(float duration, float scaleX, float scaleY) {
auto* action = new ScaleTo();
action->duration_ = duration;
action->endScale_ = Vec2(scaleX, scaleY);
return action;
}
ScaleTo* ScaleTo::create(float duration, const Vec2& scale) {
return create(duration, scale.x, scale.y);
}
void ScaleTo::onStart() {
startScale_ = target_->getScale();
delta_ = endScale_ - startScale_;
}
void ScaleTo::onUpdate(float progress) {
Vec2 newScale = startScale_ + delta_ * progress;
target_->setScale(newScale);
}
ActionInterval* ScaleTo::clone() const {
return ScaleTo::create(duration_, endScale_);
}
ActionInterval* ScaleTo::reverse() const {
return ScaleTo::create(duration_, startScale_);
}
// ============================================================================
// 旋转动作
// ============================================================================
RotateBy* RotateBy::create(float duration, float deltaAngle) {
auto* action = new RotateBy();
action->duration_ = duration;
action->deltaAngle_ = deltaAngle;
return action;
}
void RotateBy::onStart() {
startAngle_ = target_->getRotation();
}
void RotateBy::onUpdate(float progress) {
float newAngle = startAngle_ + deltaAngle_ * progress;
target_->setRotation(newAngle);
}
ActionInterval* RotateBy::clone() const {
return RotateBy::create(duration_, deltaAngle_);
}
ActionInterval* RotateBy::reverse() const {
return RotateBy::create(duration_, -deltaAngle_);
}
// RotateTo
RotateTo* RotateTo::create(float duration, float angle) {
auto* action = new RotateTo();
action->duration_ = duration;
action->endAngle_ = angle;
return action;
}
void RotateTo::onStart() {
startAngle_ = target_->getRotation();
deltaAngle_ = endAngle_ - startAngle_;
if (deltaAngle_ > 180.0f) deltaAngle_ -= 360.0f;
if (deltaAngle_ < -180.0f) deltaAngle_ += 360.0f;
}
void RotateTo::onUpdate(float progress) {
float newAngle = startAngle_ + deltaAngle_ * progress;
target_->setRotation(newAngle);
}
ActionInterval* RotateTo::clone() const {
return RotateTo::create(duration_, endAngle_);
}
ActionInterval* RotateTo::reverse() const {
return RotateTo::create(duration_, startAngle_);
}
// ============================================================================
// 淡入淡出动作
// ============================================================================
FadeIn* FadeIn::create(float duration) {
auto* action = new FadeIn();
action->duration_ = duration;
return action;
}
void FadeIn::onStart() {
startOpacity_ = target_->getOpacity();
target_->setOpacity(0.0f);
}
void FadeIn::onUpdate(float progress) {
target_->setOpacity(progress);
}
ActionInterval* FadeIn::clone() const {
return FadeIn::create(duration_);
}
ActionInterval* FadeIn::reverse() const {
return FadeOut::create(duration_);
}
// FadeOut
FadeOut* FadeOut::create(float duration) {
auto* action = new FadeOut();
action->duration_ = duration;
return action;
}
void FadeOut::onStart() {
startOpacity_ = target_->getOpacity();
target_->setOpacity(1.0f);
}
void FadeOut::onUpdate(float progress) {
target_->setOpacity(1.0f - progress);
}
ActionInterval* FadeOut::clone() const {
return FadeOut::create(duration_);
}
ActionInterval* FadeOut::reverse() const {
return FadeIn::create(duration_);
}
// FadeTo
FadeTo* FadeTo::create(float duration, float opacity) {
auto* action = new FadeTo();
action->duration_ = duration;
action->endOpacity_ = opacity;
return action;
}
void FadeTo::onStart() {
startOpacity_ = target_->getOpacity();
deltaOpacity_ = endOpacity_ - startOpacity_;
}
void FadeTo::onUpdate(float progress) {
target_->setOpacity(startOpacity_ + deltaOpacity_ * progress);
}
ActionInterval* FadeTo::clone() const {
return FadeTo::create(duration_, endOpacity_);
}
ActionInterval* FadeTo::reverse() const {
return FadeTo::create(duration_, startOpacity_);
}
// ============================================================================
// 闪烁动作
// ============================================================================
Blink* Blink::create(float duration, int times) {
auto* action = new Blink();
action->duration_ = duration;
action->times_ = times;
return action;
}
void Blink::onStart() {
originalVisible_ = target_->isVisible();
currentTimes_ = 0;
}
void Blink::onUpdate(float progress) {
float slice = 1.0f / times_;
float m = fmodf(progress, slice);
target_->setVisible(m > slice / 2.0f);
}
ActionInterval* Blink::clone() const {
return Blink::create(duration_, times_);
}
ActionInterval* Blink::reverse() const {
return Blink::create(duration_, times_);
}
// ============================================================================
// 色调动作
// ============================================================================
TintTo* TintTo::create(float duration, uint8_t red, uint8_t green, uint8_t blue) {
auto* action = new TintTo();
action->duration_ = duration;
action->endColor_ = Color3B(red, green, blue);
return action;
}
void TintTo::onStart() {
startColor_ = target_->getColor();
deltaColor_ = Color3B(
static_cast<int16_t>(endColor_.r) - static_cast<int16_t>(startColor_.r),
static_cast<int16_t>(endColor_.g) - static_cast<int16_t>(startColor_.g),
static_cast<int16_t>(endColor_.b) - static_cast<int16_t>(startColor_.b)
);
}
void TintTo::onUpdate(float progress) {
Color3B newColor(
static_cast<uint8_t>(startColor_.r + deltaColor_.r * progress),
static_cast<uint8_t>(startColor_.g + deltaColor_.g * progress),
static_cast<uint8_t>(startColor_.b + deltaColor_.b * progress)
);
target_->setColor(newColor);
}
ActionInterval* TintTo::clone() const {
return TintTo::create(duration_, endColor_.r, endColor_.g, endColor_.b);
}
ActionInterval* TintTo::reverse() const {
return TintTo::create(duration_, startColor_.r, startColor_.g, startColor_.b);
}
// TintBy
TintBy* TintBy::create(float duration, int16_t deltaRed, int16_t deltaGreen, int16_t deltaBlue) {
auto* action = new TintBy();
action->duration_ = duration;
action->deltaR_ = deltaRed;
action->deltaG_ = deltaGreen;
action->deltaB_ = deltaBlue;
return action;
}
void TintBy::onStart() {
startColor_ = target_->getColor();
}
void TintBy::onUpdate(float progress) {
Color3B newColor(
static_cast<uint8_t>(startColor_.r + deltaR_ * progress),
static_cast<uint8_t>(startColor_.g + deltaG_ * progress),
static_cast<uint8_t>(startColor_.b + deltaB_ * progress)
);
target_->setColor(newColor);
}
ActionInterval* TintBy::clone() const {
return TintBy::create(duration_, deltaR_, deltaG_, deltaB_);
}
ActionInterval* TintBy::reverse() const {
return TintBy::create(duration_, -deltaR_, -deltaG_, -deltaB_);
}
// ============================================================================
// 组合动作
// ============================================================================
Sequence* Sequence::create(ActionInterval* action1, ...) {
std::vector<ActionInterval*> actions;
actions.push_back(action1);
va_list args;
va_start(args, action1);
ActionInterval* action = nullptr;
while ((action = va_arg(args, ActionInterval*)) != nullptr) {
actions.push_back(action);
}
va_end(args);
return create(actions);
}
Sequence* Sequence::create(const std::vector<ActionInterval*>& actions) {
auto* seq = new Sequence();
seq->duration_ = 0.0f;
for (auto* action : actions) {
if (action) {
seq->actions_.push_back(action);
seq->duration_ += action->getDuration();
}
}
return seq;
}
Sequence::~Sequence() {
for (auto* action : actions_) {
delete action;
}
}
void Sequence::onStart() {
currentIndex_ = 0;
split_ = 0.0f;
last_ = -1.0f;
if (!actions_.empty()) {
actions_[0]->startWithTarget(target_);
}
}
void Sequence::onUpdate(float progress) {
float newTime = progress * duration_;
if (newTime < last_) {
for (auto* action : actions_) {
action->stop();
}
}
last_ = newTime;
float foundSplit = 0.0f;
size_t found = 0;
for (size_t i = 0; i < actions_.size(); ++i) {
foundSplit += actions_[i]->getDuration();
if (foundSplit > newTime) {
found = i;
break;
} else if (foundSplit == newTime) {
found = i + 1;
break;
}
}
if (found != currentIndex_) {
if (currentIndex_ < actions_.size()) {
actions_[currentIndex_]->update(1.0f);
}
if (found < actions_.size()) {
actions_[found]->startWithTarget(target_);
}
currentIndex_ = found;
}
if (currentIndex_ < actions_.size()) {
float localTime = newTime - (foundSplit - actions_[currentIndex_]->getDuration());
float localProgress = actions_[currentIndex_]->getDuration() > 0.0f
? localTime / actions_[currentIndex_]->getDuration() : 1.0f;
actions_[currentIndex_]->update(localProgress);
}
}
ActionInterval* Sequence::clone() const {
std::vector<ActionInterval*> cloned;
for (auto* action : actions_) {
cloned.push_back(action->clone());
}
return Sequence::create(cloned);
}
ActionInterval* Sequence::reverse() const {
std::vector<ActionInterval*> rev;
for (auto it = actions_.rbegin(); it != actions_.rend(); ++it) {
rev.push_back((*it)->reverse());
}
return Sequence::create(rev);
}
// Spawn
Spawn* Spawn::create(ActionInterval* action1, ...) {
std::vector<ActionInterval*> actions;
actions.push_back(action1);
va_list args;
va_start(args, action1);
ActionInterval* action = nullptr;
while ((action = va_arg(args, ActionInterval*)) != nullptr) {
actions.push_back(action);
}
va_end(args);
return create(actions);
}
Spawn* Spawn::create(const std::vector<ActionInterval*>& actions) {
auto* spawn = new Spawn();
spawn->duration_ = 0.0f;
for (auto* action : actions) {
if (action) {
spawn->actions_.push_back(action);
spawn->duration_ = std::max(spawn->duration_, action->getDuration());
}
}
return spawn;
}
Spawn::~Spawn() {
for (auto* action : actions_) {
delete action;
}
}
void Spawn::onStart() {
for (auto* action : actions_) {
action->startWithTarget(target_);
}
}
void Spawn::onUpdate(float progress) {
for (auto* action : actions_) {
float localProgress = action->getDuration() > 0.0f
? std::min(1.0f, (progress * duration_) / action->getDuration())
: 1.0f;
action->update(localProgress);
}
}
ActionInterval* Spawn::clone() const {
std::vector<ActionInterval*> cloned;
for (auto* action : actions_) {
cloned.push_back(action->clone());
}
return Spawn::create(cloned);
}
ActionInterval* Spawn::reverse() const {
std::vector<ActionInterval*> rev;
for (auto* action : actions_) {
rev.push_back(action->reverse());
}
return Spawn::create(rev);
}
// Repeat
Repeat* Repeat::create(ActionInterval* action, int times) {
auto* repeat = new Repeat();
repeat->innerAction_ = action;
repeat->times_ = times;
repeat->duration_ = action->getDuration() * times;
return repeat;
}
ActionInterval* Repeat::clone() const {
return Repeat::create(innerAction_->clone(), times_);
}
ActionInterval* Repeat::reverse() const {
return Repeat::create(innerAction_->reverse(), times_);
}
bool Repeat::isDone() const {
return currentTimes_ >= times_;
}
void Repeat::onStart() {
currentTimes_ = 0;
innerAction_->startWithTarget(target_);
}
void Repeat::onUpdate(float progress) {
float t = progress * times_;
int current = static_cast<int>(t);
if (current > currentTimes_) {
innerAction_->update(1.0f);
currentTimes_++;
if (currentTimes_ < times_) {
innerAction_->startWithTarget(target_);
}
}
if (currentTimes_ < times_) {
innerAction_->update(t - current);
}
}
// RepeatForever
RepeatForever* RepeatForever::create(ActionInterval* action) {
auto* repeat = new RepeatForever();
repeat->innerAction_ = action;
repeat->duration_ = action->getDuration();
return repeat;
}
ActionInterval* RepeatForever::clone() const {
return RepeatForever::create(innerAction_->clone());
}
ActionInterval* RepeatForever::reverse() const {
return RepeatForever::create(innerAction_->reverse());
}
bool RepeatForever::isDone() const {
return false;
}
void RepeatForever::onStart() {
innerAction_->startWithTarget(target_);
}
void RepeatForever::onUpdate(float progress) {
innerAction_->update(progress);
if (innerAction_->isDone()) {
innerAction_->startWithTarget(target_);
elapsed_ = 0.0f;
}
}
// DelayTime
DelayTime* DelayTime::create(float duration) {
auto* delay = new DelayTime();
delay->duration_ = duration;
return delay;
}
ActionInterval* DelayTime::clone() const {
return DelayTime::create(duration_);
}
ActionInterval* DelayTime::reverse() const {
return DelayTime::create(duration_);
}
// ReverseTime
ReverseTime* ReverseTime::create(ActionInterval* action) {
auto* rev = new ReverseTime();
rev->innerAction_ = action;
rev->duration_ = action->getDuration();
return rev;
}
ReverseTime::~ReverseTime() {
delete innerAction_;
}
ActionInterval* ReverseTime::clone() const {
return ReverseTime::create(innerAction_->clone());
}
ActionInterval* ReverseTime::reverse() const {
return innerAction_->clone();
}
void ReverseTime::onStart() {
innerAction_->startWithTarget(target_);
}
void ReverseTime::onUpdate(float progress) {
innerAction_->update(1.0f - progress);
}
} // namespace extra2d

View File

@ -1,244 +0,0 @@
#include "extra2d/action/action_manager.h"
#include "extra2d/scene/node.h"
#include "extra2d/utils/logger.h"
namespace extra2d {
ActionManager* ActionManager::instance_ = nullptr;
ActionManager::ActionManager() {}
ActionManager::~ActionManager() {
removeAllActions();
}
ActionManager* ActionManager::getInstance() {
if (!instance_) {
instance_ = new ActionManager();
}
return instance_;
}
void ActionManager::destroyInstance() {
if (instance_) {
delete instance_;
instance_ = nullptr;
}
}
void ActionManager::addAction(Action* action, Node* target, bool paused) {
if (!action || !target) {
return;
}
auto it = targets_.find(target);
if (it == targets_.end()) {
ActionElement element;
element.target = target;
element.paused = paused;
targets_[target] = element;
it = targets_.find(target);
}
auto& element = it->second;
element.actions.push_back(action);
action->startWithTarget(target);
}
void ActionManager::removeAction(Action* action) {
if (!action) {
return;
}
Node* target = action->getOriginalTarget();
if (!target) {
return;
}
auto it = targets_.find(target);
if (it == targets_.end()) {
return;
}
auto& element = it->second;
for (size_t i = 0; i < element.actions.size(); ++i) {
if (element.actions[i] == action) {
removeActionAt(i, element);
break;
}
}
}
void ActionManager::removeActionByTag(int tag, Node* target) {
if (!target) {
return;
}
auto it = targets_.find(target);
if (it == targets_.end()) {
return;
}
auto& element = it->second;
for (size_t i = 0; i < element.actions.size(); ++i) {
if (element.actions[i]->getTag() == tag) {
removeActionAt(i, element);
break;
}
}
}
void ActionManager::removeActionsByFlags(unsigned int flags, Node* target) {
if (!target) {
return;
}
auto it = targets_.find(target);
if (it == targets_.end()) {
return;
}
auto& element = it->second;
for (int i = static_cast<int>(element.actions.size()) - 1; i >= 0; --i) {
if (element.actions[i]->getFlags() & flags) {
removeActionAt(i, element);
}
}
}
void ActionManager::removeAllActionsFromTarget(Node* target) {
if (!target) {
return;
}
auto it = targets_.find(target);
if (it == targets_.end()) {
return;
}
auto& element = it->second;
for (auto* action : element.actions) {
action->stop();
deleteAction(action);
}
element.actions.clear();
targets_.erase(it);
}
void ActionManager::removeAllActions() {
for (auto& pair : targets_) {
auto& element = pair.second;
for (auto* action : element.actions) {
action->stop();
deleteAction(action);
}
}
targets_.clear();
}
Action* ActionManager::getActionByTag(int tag, Node* target) {
if (!target) {
return nullptr;
}
auto it = targets_.find(target);
if (it == targets_.end()) {
return nullptr;
}
auto& element = it->second;
for (auto* action : element.actions) {
if (action->getTag() == tag) {
return action;
}
}
return nullptr;
}
size_t ActionManager::getActionCount(Node* target) const {
auto it = targets_.find(target);
if (it == targets_.end()) {
return 0;
}
return it->second.actions.size();
}
void ActionManager::pauseTarget(Node* target) {
auto it = targets_.find(target);
if (it != targets_.end()) {
it->second.paused = true;
}
}
void ActionManager::resumeTarget(Node* target) {
auto it = targets_.find(target);
if (it != targets_.end()) {
it->second.paused = false;
}
}
bool ActionManager::isPaused(Node* target) const {
auto it = targets_.find(target);
if (it == targets_.end()) {
return false;
}
return it->second.paused;
}
void ActionManager::update(float dt) {
for (auto it = targets_.begin(); it != targets_.end(); ) {
auto& element = it->second;
Node* target = element.target;
if (!element.paused && target) {
element.actionIndex = 0;
while (static_cast<size_t>(element.actionIndex) < element.actions.size()) {
element.currentAction = element.actions[element.actionIndex];
element.currentActionSalvaged = false;
if (element.currentAction->getState() != ActionState::Paused) {
element.currentAction->step(dt);
}
if (element.currentActionSalvaged) {
deleteAction(element.currentAction);
} else if (element.currentAction->isDone()) {
element.currentAction->stop();
deleteAction(element.currentAction);
element.actions.erase(element.actions.begin() + element.actionIndex);
continue;
}
element.actionIndex++;
}
}
if (element.actions.empty()) {
it = targets_.erase(it);
} else {
++it;
}
}
}
void ActionManager::removeActionAt(size_t index, ActionElement& element) {
if (index >= element.actions.size()) {
return;
}
Action* action = element.actions[index];
if (action == element.currentAction) {
element.currentActionSalvaged = true;
}
action->stop();
deleteAction(action);
element.actions.erase(element.actions.begin() + index);
}
void ActionManager::deleteAction(Action* action) {
if (action) {
delete action;
}
}
} // namespace extra2d

View File

@ -1,172 +0,0 @@
#include "extra2d/action/action_special.h"
#include "extra2d/scene/node.h"
namespace extra2d {
// ============================================================================
// Speed
// ============================================================================
Speed* Speed::create(ActionInterval* action, float speed) {
auto* speedAction = new Speed();
speedAction->innerAction_ = action;
speedAction->speed_ = speed;
return speedAction;
}
Speed::~Speed() {
delete innerAction_;
}
void Speed::startWithTarget(Node* target) {
Action::startWithTarget(target);
innerAction_->startWithTarget(target);
}
void Speed::stop() {
innerAction_->stop();
Action::stop();
}
void Speed::step(float dt) {
if (state_ != ActionState::Running) {
return;
}
innerAction_->step(dt * speed_);
if (innerAction_->isDone()) {
state_ = ActionState::Completed;
onComplete();
}
}
bool Speed::isDone() const {
return innerAction_->isDone();
}
Action* Speed::clone() const {
return Speed::create(innerAction_->clone(), speed_);
}
Action* Speed::reverse() const {
return Speed::create(innerAction_->reverse(), speed_);
}
// ============================================================================
// Follow
// ============================================================================
Follow* Follow::create(Node* followedNode) {
return create(followedNode, Rect::Zero());
}
Follow* Follow::create(Node* followedNode, const Rect& boundary) {
auto* follow = new Follow();
follow->followedNode_ = followedNode;
follow->boundary_ = boundary;
follow->boundarySet_ = (boundary != Rect::Zero());
return follow;
}
Follow::~Follow() {
followedNode_ = nullptr;
}
void Follow::startWithTarget(Node* target) {
Action::startWithTarget(target);
if (target && followedNode_) {
halfScreenSize_ = Vec2(0, 0);
fullScreenSize_ = Vec2(0, 0);
if (boundarySet_) {
leftBoundary_ = Vec2(boundary_.origin.x, 0);
rightBoundary_ = Vec2(boundary_.origin.x + boundary_.size.width, 0);
topBoundary_ = Vec2(0, boundary_.origin.y);
bottomBoundary_ = Vec2(0, boundary_.origin.y + boundary_.size.height);
}
}
}
void Follow::stop() {
followedNode_ = nullptr;
Action::stop();
}
void Follow::step(float dt) {
(void)dt;
if (state_ != ActionState::Running || !followedNode_ || !target_) {
return;
}
Vec2 pos = followedNode_->getPosition();
if (boundarySet_) {
pos.x = std::clamp(pos.x, leftBoundary_.x, rightBoundary_.x);
pos.y = std::clamp(pos.y, bottomBoundary_.y, topBoundary_.y);
}
target_->setPosition(pos);
}
bool Follow::isDone() const {
return followedNode_ == nullptr || !followedNode_->isRunning();
}
Action* Follow::clone() const {
return Follow::create(followedNode_, boundary_);
}
Action* Follow::reverse() const {
return Follow::create(followedNode_, boundary_);
}
// ============================================================================
// TargetedAction
// ============================================================================
TargetedAction* TargetedAction::create(Node* target, FiniteTimeAction* action) {
auto* targeted = new TargetedAction();
targeted->targetNode_ = target;
targeted->innerAction_ = action;
return targeted;
}
TargetedAction::~TargetedAction() {
delete innerAction_;
}
void TargetedAction::startWithTarget(Node* target) {
Action::startWithTarget(target);
if (targetNode_) {
innerAction_->startWithTarget(targetNode_);
}
}
void TargetedAction::stop() {
innerAction_->stop();
Action::stop();
}
void TargetedAction::step(float dt) {
if (state_ != ActionState::Running) {
return;
}
innerAction_->step(dt);
if (innerAction_->isDone()) {
state_ = ActionState::Completed;
onComplete();
}
}
bool TargetedAction::isDone() const {
return innerAction_->isDone();
}
Action* TargetedAction::clone() const {
return TargetedAction::create(targetNode_, innerAction_->clone());
}
Action* TargetedAction::reverse() const {
return TargetedAction::create(targetNode_, innerAction_->reverse());
}
} // namespace extra2d

View File

@ -1,225 +0,0 @@
#include "extra2d/action/ease.h"
#include <cmath>
namespace extra2d {
#ifndef M_PI
#define M_PI 3.14159265358979323846f
#endif
// ============================================================================
// 线性缓动
// ============================================================================
float easeLinear(float t) {
return t;
}
// ============================================================================
// 二次缓动 (Quad)
// ============================================================================
float easeInQuad(float t) {
return t * t;
}
float easeOutQuad(float t) {
return 1.0f - (1.0f - t) * (1.0f - t);
}
float easeInOutQuad(float t) {
return t < 0.5f ? 2.0f * t * t : 1.0f - std::pow(-2.0f * t + 2.0f, 2.0f) / 2.0f;
}
// ============================================================================
// 三次缓动 (Cubic)
// ============================================================================
float easeInCubic(float t) {
return t * t * t;
}
float easeOutCubic(float t) {
return 1.0f - std::pow(1.0f - t, 3.0f);
}
float easeInOutCubic(float t) {
return t < 0.5f ? 4.0f * t * t * t : 1.0f - std::pow(-2.0f * t + 2.0f, 3.0f) / 2.0f;
}
// ============================================================================
// 四次缓动 (Quart)
// ============================================================================
float easeInQuart(float t) {
return t * t * t * t;
}
float easeOutQuart(float t) {
return 1.0f - std::pow(1.0f - t, 4.0f);
}
float easeInOutQuart(float t) {
return t < 0.5f ? 8.0f * t * t * t * t : 1.0f - std::pow(-2.0f * t + 2.0f, 4.0f) / 2.0f;
}
// ============================================================================
// 五次缓动 (Quint)
// ============================================================================
float easeInQuint(float t) {
return t * t * t * t * t;
}
float easeOutQuint(float t) {
return 1.0f - std::pow(1.0f - t, 5.0f);
}
float easeInOutQuint(float t) {
return t < 0.5f ? 16.0f * t * t * t * t * t : 1.0f - std::pow(-2.0f * t + 2.0f, 5.0f) / 2.0f;
}
// ============================================================================
// 正弦缓动 (Sine)
// ============================================================================
float easeInSine(float t) {
return 1.0f - std::cos((t * M_PI) / 2.0f);
}
float easeOutSine(float t) {
return std::sin((t * M_PI) / 2.0f);
}
float easeInOutSine(float t) {
return -(std::cos(M_PI * t) - 1.0f) / 2.0f;
}
// ============================================================================
// 指数缓动 (Exponential)
// ============================================================================
float easeInExpo(float t) {
return t == 0.0f ? 0.0f : std::pow(2.0f, 10.0f * (t - 1.0f));
}
float easeOutExpo(float t) {
return t == 1.0f ? 1.0f : 1.0f - std::pow(2.0f, -10.0f * t);
}
float easeInOutExpo(float t) {
if (t == 0.0f) return 0.0f;
if (t == 1.0f) return 1.0f;
return t < 0.5f
? std::pow(2.0f, 20.0f * t - 10.0f) / 2.0f
: (2.0f - std::pow(2.0f, -20.0f * t + 10.0f)) / 2.0f;
}
// ============================================================================
// 圆形缓动 (Circular)
// ============================================================================
float easeInCirc(float t) {
return 1.0f - std::sqrt(1.0f - std::pow(t, 2.0f));
}
float easeOutCirc(float t) {
return std::sqrt(1.0f - std::pow(t - 1.0f, 2.0f));
}
float easeInOutCirc(float t) {
return t < 0.5f
? (1.0f - std::sqrt(1.0f - std::pow(2.0f * t, 2.0f))) / 2.0f
: (std::sqrt(1.0f - std::pow(-2.0f * t + 2.0f, 2.0f)) + 1.0f) / 2.0f;
}
// ============================================================================
// 回震缓动 (Back)
// ============================================================================
float easeInBack(float t) {
const float c1 = 1.70158f;
const float c3 = c1 + 1.0f;
return c3 * t * t * t - c1 * t * t;
}
float easeOutBack(float t) {
const float c1 = 1.70158f;
const float c3 = c1 + 1.0f;
return 1.0f + c3 * std::pow(t - 1.0f, 3.0f) + c1 * std::pow(t - 1.0f, 2.0f);
}
float easeInOutBack(float t) {
const float c1 = 1.70158f;
const float c2 = c1 * 1.525f;
return t < 0.5f
? (std::pow(2.0f * t, 2.0f) * ((c2 + 1.0f) * 2.0f * t - c2)) / 2.0f
: (std::pow(2.0f * t - 2.0f, 2.0f) * ((c2 + 1.0f) * (t * 2.0f - 2.0f) + c2) + 2.0f) / 2.0f;
}
// ============================================================================
// 弹性缓动 (Elastic)
// ============================================================================
float easeInElastic(float t) {
const float c4 = (2.0f * M_PI) / 3.0f;
if (t == 0.0f) return 0.0f;
if (t == 1.0f) return 1.0f;
return -std::pow(2.0f, 10.0f * t - 10.0f) * std::sin((t * 10.0f - 10.75f) * c4);
}
float easeOutElastic(float t) {
const float c4 = (2.0f * M_PI) / 3.0f;
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 * 10.0f - 0.75f) * c4) + 1.0f;
}
float easeInOutElastic(float t) {
const float c5 = (2.0f * M_PI) / 4.5f;
if (t == 0.0f) return 0.0f;
if (t == 1.0f) return 1.0f;
return t < 0.5f
? -(std::pow(2.0f, 20.0f * t - 10.0f) * std::sin((20.0f * t - 11.125f) * c5)) / 2.0f
: (std::pow(2.0f, -20.0f * t + 10.0f) * std::sin((20.0f * t - 11.125f) * c5)) / 2.0f + 1.0f;
}
// ============================================================================
// 弹跳缓动 (Bounce)
// ============================================================================
namespace {
float easeOutBounceInternal(float t) {
const float n1 = 7.5625f;
const float d1 = 2.75f;
if (t < 1.0f / d1) {
return n1 * t * t;
} else if (t < 2.0f / d1) {
t -= 1.5f / d1;
return n1 * t * t + 0.75f;
} else if (t < 2.5f / d1) {
t -= 2.25f / d1;
return n1 * t * t + 0.9375f;
} else {
t -= 2.625f / d1;
return n1 * t * t + 0.984375f;
}
}
}
float easeInBounce(float t) {
return 1.0f - easeOutBounceInternal(1.0f - t);
}
float easeOutBounce(float t) {
return easeOutBounceInternal(t);
}
float easeInOutBounce(float t) {
return t < 0.5f
? (1.0f - easeOutBounceInternal(1.0f - 2.0f * t)) / 2.0f
: (1.0f + easeOutBounceInternal(2.0f * t - 1.0f)) / 2.0f;
}
} // namespace extra2d

View File

@ -1,9 +0,0 @@
#include "extra2d/action/finite_time_action.h"
namespace extra2d {
FiniteTimeAction::FiniteTimeAction(float duration)
: duration_(duration) {
}
} // namespace extra2d

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,3 @@
#include <extra2d/action/action_manager.h>
#include <extra2d/app/application.h>
#include <extra2d/audio/audio_engine.h>
#include <extra2d/event/event_dispatcher.h>
@ -342,9 +341,6 @@ void Application::mainLoop() {
}
void Application::update() {
// Update action manager (single update per frame)
ActionManager::getInstance()->update(deltaTime_);
if (timerManager_) {
timerManager_->update(deltaTime_);
}

View File

@ -1,918 +0,0 @@
#include <extra2d/effects/custom_effect_manager.h>
#include <extra2d/utils/logger.h>
#include <fstream>
#include <json/json.hpp>
#include <sstream>
namespace extra2d {
using json = nlohmann::json;
// ============================================================================
// JSON序列化辅助函数
// ============================================================================
/**
* @brief Vec2转换为JSON数组
*/
static json vec2ToJson(const Vec2 &v) { return json::array({v.x, v.y}); }
/**
* @brief JSON数组解析Vec2
*/
static Vec2 jsonToVec2(const json &j) {
if (j.is_array() && j.size() >= 2) {
return Vec2(j[0].get<float>(), j[1].get<float>());
}
return Vec2();
}
/**
* @brief Color转换为JSON数组
*/
static json colorToJson(const Color &c) {
return json::array({c.r, c.g, c.b, c.a});
}
/**
* @brief JSON数组解析Color
*/
static Color jsonToColor(const json &j) {
if (j.is_array() && j.size() >= 4) {
return Color(j[0].get<float>(), j[1].get<float>(), j[2].get<float>(),
j[3].get<float>());
}
return Colors::White;
}
/**
* @brief EmitterConfig转换为JSON
*/
static json emitterConfigToJson(const EmitterConfig &config) {
json j;
j["emissionRate"] = config.emissionRate;
j["life"] = json::array({config.minLife, config.maxLife});
j["startSize"] = json::array({config.minStartSize, config.maxStartSize});
j["endSize"] = json::array({config.minEndSize, config.maxEndSize});
j["velocity"] = json::object({{"min", vec2ToJson(config.minVelocity)},
{"max", vec2ToJson(config.maxVelocity)}});
j["acceleration"] = vec2ToJson(config.acceleration);
j["startColor"] = colorToJson(config.startColor);
j["endColor"] = colorToJson(config.endColor);
j["blendMode"] = static_cast<int>(config.blendMode);
j["shape"] = static_cast<int>(config.shape);
j["shapeRadius"] = config.shapeRadius;
return j;
}
/**
* @brief JSON解析EmitterConfig
*/
static EmitterConfig jsonToEmitterConfig(const json &j) {
EmitterConfig config;
if (j.contains("emissionRate")) {
config.emissionRate = j["emissionRate"].get<float>();
}
if (j.contains("life") && j["life"].is_array() && j["life"].size() >= 2) {
config.minLife = j["life"][0].get<float>();
config.maxLife = j["life"][1].get<float>();
}
if (j.contains("startSize") && j["startSize"].is_array() &&
j["startSize"].size() >= 2) {
config.minStartSize = j["startSize"][0].get<float>();
config.maxStartSize = j["startSize"][1].get<float>();
}
if (j.contains("endSize") && j["endSize"].is_array() &&
j["endSize"].size() >= 2) {
config.minEndSize = j["endSize"][0].get<float>();
config.maxEndSize = j["endSize"][1].get<float>();
}
if (j.contains("velocity")) {
const auto &vel = j["velocity"];
if (vel.contains("min")) {
config.minVelocity = jsonToVec2(vel["min"]);
}
if (vel.contains("max")) {
config.maxVelocity = jsonToVec2(vel["max"]);
}
}
if (j.contains("acceleration")) {
config.acceleration = jsonToVec2(j["acceleration"]);
}
if (j.contains("startColor")) {
config.startColor = jsonToColor(j["startColor"]);
}
if (j.contains("endColor")) {
config.endColor = jsonToColor(j["endColor"]);
}
if (j.contains("blendMode")) {
config.blendMode = static_cast<BlendMode>(j["blendMode"].get<int>());
}
if (j.contains("shape")) {
config.shape = static_cast<EmitterConfig::Shape>(j["shape"].get<int>());
}
if (j.contains("shapeRadius")) {
config.shapeRadius = j["shapeRadius"].get<float>();
}
return config;
}
/**
* @brief CustomEffectConfig转换为JSON
*/
static json effectConfigToJson(const CustomEffectConfig &config) {
json j;
j["name"] = config.name;
j["type"] = static_cast<int>(config.type);
j["description"] = config.description;
j["duration"] = config.duration;
j["loop"] = config.loop;
j["delay"] = config.delay;
if (config.type == CustomEffectType::Particle) {
j["emitter"] = emitterConfigToJson(config.emitterConfig);
} else if (config.type == CustomEffectType::PostProcess) {
j["shaderVert"] = config.shaderVertPath;
j["shaderFrag"] = config.shaderFragPath;
j["params"] = config.shaderParams;
}
return j;
}
/**
* @brief JSON解析CustomEffectConfig
*/
static CustomEffectConfig jsonToEffectConfig(const json &j) {
CustomEffectConfig config;
if (j.contains("name")) {
config.name = j["name"].get<std::string>();
}
if (j.contains("type")) {
config.type = static_cast<CustomEffectType>(j["type"].get<int>());
}
if (j.contains("description")) {
config.description = j["description"].get<std::string>();
}
if (j.contains("duration")) {
config.duration = j["duration"].get<float>();
}
if (j.contains("loop")) {
config.loop = j["loop"].get<bool>();
}
if (j.contains("delay")) {
config.delay = j["delay"].get<float>();
}
if (j.contains("emitter")) {
config.emitterConfig = jsonToEmitterConfig(j["emitter"]);
}
if (j.contains("shaderVert")) {
config.shaderVertPath = j["shaderVert"].get<std::string>();
}
if (j.contains("shaderFrag")) {
config.shaderFragPath = j["shaderFrag"].get<std::string>();
}
if (j.contains("params")) {
for (auto &[key, value] : j["params"].items()) {
config.shaderParams[key] = value.get<float>();
}
}
return config;
}
// ============================================================================
// CustomEffect实现
// ============================================================================
CustomEffect::CustomEffect(const CustomEffectConfig &config)
: config_(config) {}
bool CustomEffect::init() { return true; }
void CustomEffect::update(float dt) {
if (!playing_ || paused_ || finished_)
return;
// 处理延迟
if (delayTimer_ < config_.delay) {
delayTimer_ += dt;
return;
}
elapsedTime_ += dt;
// 检查是否结束
if (config_.duration > 0 && elapsedTime_ >= config_.duration) {
if (config_.loop) {
elapsedTime_ = 0.0f;
} else {
finished_ = true;
playing_ = false;
}
}
}
void CustomEffect::render(RenderBackend &renderer) {
// 基类不渲染任何内容
}
void CustomEffect::shutdown() {
playing_ = false;
paused_ = false;
finished_ = true;
}
void CustomEffect::play() {
if (finished_) {
reset();
}
playing_ = true;
paused_ = false;
}
void CustomEffect::pause() { paused_ = true; }
void CustomEffect::stop() {
playing_ = false;
paused_ = false;
}
void CustomEffect::reset() {
elapsedTime_ = 0.0f;
delayTimer_ = 0.0f;
finished_ = false;
playing_ = false;
paused_ = false;
}
// ============================================================================
// CustomParticleEffect实现
// ============================================================================
CustomParticleEffect::CustomParticleEffect(const CustomEffectConfig &config)
: CustomEffect(config) {}
bool CustomParticleEffect::init() {
particleSystem_ = ParticleSystem::create();
if (!particleSystem_) {
E2D_ERROR("创建粒子系统失败");
return false;
}
emitter_ = particleSystem_->addEmitter(config_.emitterConfig);
if (!emitter_) {
E2D_ERROR("创建粒子发射器失败");
return false;
}
// 初始化时启动发射器
emitter_->start();
return true;
}
void CustomParticleEffect::play() {
CustomEffect::play();
if (emitter_) {
emitter_->start();
}
}
void CustomParticleEffect::stop() {
CustomEffect::stop();
if (emitter_) {
emitter_->stop();
}
}
void CustomParticleEffect::update(float dt) {
CustomEffect::update(dt);
if (particleSystem_) {
particleSystem_->setPosition(position_);
particleSystem_->onUpdate(dt);
}
}
void CustomParticleEffect::render(RenderBackend &renderer) {
if (particleSystem_) {
particleSystem_->onDraw(renderer);
}
}
void CustomParticleEffect::shutdown() {
if (emitter_) {
emitter_->stop();
emitter_.reset();
}
if (particleSystem_) {
particleSystem_->removeAllEmitters();
particleSystem_.reset();
}
CustomEffect::shutdown();
}
// ============================================================================
// CustomPostProcessEffect实现
// ============================================================================
CustomPostProcessEffect::CustomPostProcessEffect(
const CustomEffectConfig &config)
: CustomEffect(config), PostProcessEffect(config.name) {}
bool CustomPostProcessEffect::init() {
if (!config_.shaderVertPath.empty() && !config_.shaderFragPath.empty()) {
if (!loadShaderFromFile(config_.shaderVertPath, config_.shaderFragPath)) {
E2D_ERROR("加载后处理Shader失败");
return false;
}
}
runtimeParams_ = config_.shaderParams;
return true;
}
void CustomPostProcessEffect::update(float dt) { CustomEffect::update(dt); }
void CustomPostProcessEffect::shutdown() {
PostProcessEffect::shutdown();
CustomEffect::shutdown();
}
void CustomPostProcessEffect::onShaderBind(GLShader &shader) {
for (const auto &[name, value] : runtimeParams_) {
shader.setFloat(name, value);
}
}
void CustomPostProcessEffect::setParam(const std::string &name, float value) {
runtimeParams_[name] = value;
}
float CustomPostProcessEffect::getParam(const std::string &name) const {
auto it = runtimeParams_.find(name);
if (it != runtimeParams_.end()) {
return it->second;
}
return 0.0f;
}
// ============================================================================
// CustomEffectFactory实现
// ============================================================================
CustomEffectFactory &CustomEffectFactory::getInstance() {
static CustomEffectFactory instance;
return instance;
}
void CustomEffectFactory::registerEffect(const std::string &typeName,
EffectCreator creator) {
creators_[typeName] = creator;
E2D_INFO("注册自定义特效类型: {}", typeName);
}
Ptr<CustomEffect>
CustomEffectFactory::create(const std::string &typeName,
const CustomEffectConfig &config) {
auto it = creators_.find(typeName);
if (it != creators_.end()) {
return it->second(config);
}
// 默认创建器
if (typeName == "Particle") {
return std::make_shared<CustomParticleEffect>(config);
} else if (typeName == "PostProcess") {
return std::make_shared<CustomPostProcessEffect>(config);
}
E2D_ERROR("未知的特效类型: {}", typeName);
return nullptr;
}
bool CustomEffectFactory::isRegistered(const std::string &typeName) const {
return creators_.find(typeName) != creators_.end();
}
std::vector<std::string> CustomEffectFactory::getRegisteredTypes() const {
std::vector<std::string> types;
for (const auto &[name, _] : creators_) {
types.push_back(name);
}
return types;
}
// ============================================================================
// CustomEffectManager实现
// ============================================================================
CustomEffectManager &CustomEffectManager::getInstance() {
static CustomEffectManager instance;
return instance;
}
bool CustomEffectManager::init() {
E2D_INFO("初始化自定义特效管理器...");
// 注册默认特效类型
auto &factory = E2D_CUSTOM_EFFECT_FACTORY();
factory.registerEffect("Particle", [](const CustomEffectConfig &config) {
return std::make_shared<CustomParticleEffect>(config);
});
factory.registerEffect("PostProcess", [](const CustomEffectConfig &config) {
return std::make_shared<CustomPostProcessEffect>(config);
});
E2D_INFO("自定义特效管理器初始化完成");
return true;
}
void CustomEffectManager::shutdown() {
E2D_INFO("关闭自定义特效管理器...");
stopAll();
activeEffects_.clear();
configs_.clear();
}
bool CustomEffectManager::loadFromFile(const std::string &filepath) {
std::ifstream file(filepath);
if (!file.is_open()) {
E2D_ERROR("无法打开特效配置文件: {}", filepath);
return false;
}
try {
// 尝试解析为JSON
json j;
file >> j;
file.close();
if (j.is_array()) {
// 多个特效配置数组
for (const auto &effectJson : j) {
auto config = jsonToEffectConfig(effectJson);
if (!config.name.empty()) {
registerConfig(config.name, config);
}
}
} else if (j.is_object()) {
// 单个特效配置
auto config = jsonToEffectConfig(j);
if (!config.name.empty()) {
registerConfig(config.name, config);
}
}
E2D_INFO("从JSON文件加载特效配置: {}", filepath);
return true;
} catch (const json::exception &e) {
// JSON解析失败回退到文本格式
file.close();
return loadFromTextFile(filepath);
}
}
bool CustomEffectManager::loadFromTextFile(const std::string &filepath) {
std::ifstream file(filepath);
if (!file.is_open()) {
E2D_ERROR("无法打开特效配置文件: {}", filepath);
return false;
}
// 简化格式:每行一个特效配置
// 格式: EFFECT name type
// PARAM key value
// END
std::string line;
CustomEffectConfig currentConfig;
bool inEffect = false;
while (std::getline(file, line)) {
// 跳过空行和注释
if (line.empty() || line[0] == '#')
continue;
std::istringstream iss(line);
std::string cmd;
iss >> cmd;
if (cmd == "EFFECT") {
// 开始新特效
if (inEffect) {
// 保存上一个特效
registerConfig(currentConfig.name, currentConfig);
}
inEffect = true;
currentConfig = CustomEffectConfig();
std::string type;
iss >> currentConfig.name >> type;
if (type == "Particle") {
currentConfig.type = CustomEffectType::Particle;
} else if (type == "PostProcess") {
currentConfig.type = CustomEffectType::PostProcess;
} else {
currentConfig.type = CustomEffectType::Particle;
}
} else if (cmd == "DESC") {
std::getline(iss, currentConfig.description);
// 去除前导空格
if (!currentConfig.description.empty() &&
currentConfig.description[0] == ' ') {
currentConfig.description = currentConfig.description.substr(1);
}
} else if (cmd == "DURATION") {
iss >> currentConfig.duration;
} else if (cmd == "LOOP") {
std::string val;
iss >> val;
currentConfig.loop = (val == "true" || val == "1");
} else if (cmd == "EMISSION") {
iss >> currentConfig.emitterConfig.emissionRate;
} else if (cmd == "LIFE") {
iss >> currentConfig.emitterConfig.minLife >>
currentConfig.emitterConfig.maxLife;
} else if (cmd == "SIZE_START") {
iss >> currentConfig.emitterConfig.minStartSize >>
currentConfig.emitterConfig.maxStartSize;
} else if (cmd == "SIZE_END") {
iss >> currentConfig.emitterConfig.minEndSize >>
currentConfig.emitterConfig.maxEndSize;
} else if (cmd == "VELOCITY") {
iss >> currentConfig.emitterConfig.minVelocity.x >>
currentConfig.emitterConfig.minVelocity.y >>
currentConfig.emitterConfig.maxVelocity.x >>
currentConfig.emitterConfig.maxVelocity.y;
} else if (cmd == "ACCEL") {
iss >> currentConfig.emitterConfig.acceleration.x >>
currentConfig.emitterConfig.acceleration.y;
} else if (cmd == "COLOR_START") {
iss >> currentConfig.emitterConfig.startColor.r >>
currentConfig.emitterConfig.startColor.g >>
currentConfig.emitterConfig.startColor.b >>
currentConfig.emitterConfig.startColor.a;
} else if (cmd == "COLOR_END") {
iss >> currentConfig.emitterConfig.endColor.r >>
currentConfig.emitterConfig.endColor.g >>
currentConfig.emitterConfig.endColor.b >>
currentConfig.emitterConfig.endColor.a;
} else if (cmd == "BLEND") {
std::string mode;
iss >> mode;
if (mode == "Additive") {
currentConfig.emitterConfig.blendMode = BlendMode::Additive;
} else if (mode == "Alpha") {
currentConfig.emitterConfig.blendMode = BlendMode::Alpha;
} else {
currentConfig.emitterConfig.blendMode = BlendMode::None;
}
} else if (cmd == "END") {
// 结束当前特效
if (inEffect) {
registerConfig(currentConfig.name, currentConfig);
inEffect = false;
}
}
}
// 保存最后一个特效
if (inEffect) {
registerConfig(currentConfig.name, currentConfig);
}
file.close();
E2D_INFO("从文本文件加载特效配置: {}", filepath);
return true;
}
bool CustomEffectManager::saveToFile(const std::string &name,
const std::string &filepath,
bool useJson) {
auto it = configs_.find(name);
if (it == configs_.end()) {
E2D_ERROR("特效配置不存在: {}", name);
return false;
}
std::ofstream file(filepath);
if (!file.is_open()) {
E2D_ERROR("无法创建文件: {}", filepath);
return false;
}
if (useJson) {
// 保存为JSON格式
json j = effectConfigToJson(it->second);
file << j.dump(2); // 缩进2个空格更易读
E2D_INFO("保存特效配置到JSON文件: {}", filepath);
} else {
// 保存为文本格式
const auto &config = it->second;
file << "# Easy2D Custom Effect Config\n";
file << "# Generated automatically\n\n";
file << "EFFECT " << config.name << " ";
if (config.type == CustomEffectType::Particle) {
file << "Particle\n";
} else if (config.type == CustomEffectType::PostProcess) {
file << "PostProcess\n";
} else {
file << "Particle\n";
}
file << "DESC " << config.description << "\n";
file << "DURATION " << config.duration << "\n";
file << "LOOP " << (config.loop ? "true" : "false") << "\n";
if (config.type == CustomEffectType::Particle) {
const auto &ec = config.emitterConfig;
file << "EMISSION " << ec.emissionRate << "\n";
file << "LIFE " << ec.minLife << " " << ec.maxLife << "\n";
file << "SIZE_START " << ec.minStartSize << " " << ec.maxStartSize
<< "\n";
file << "SIZE_END " << ec.minEndSize << " " << ec.maxEndSize << "\n";
file << "VELOCITY " << ec.minVelocity.x << " " << ec.minVelocity.y << " "
<< ec.maxVelocity.x << " " << ec.maxVelocity.y << "\n";
file << "ACCEL " << ec.acceleration.x << " " << ec.acceleration.y << "\n";
file << "COLOR_START " << ec.startColor.r << " " << ec.startColor.g << " "
<< ec.startColor.b << " " << ec.startColor.a << "\n";
file << "COLOR_END " << ec.endColor.r << " " << ec.endColor.g << " "
<< ec.endColor.b << " " << ec.endColor.a << "\n";
file << "BLEND ";
switch (ec.blendMode) {
case BlendMode::Additive:
file << "Additive\n";
break;
case BlendMode::Alpha:
file << "Alpha\n";
break;
default:
file << "None\n";
break;
}
}
file << "END\n";
E2D_INFO("保存特效配置到文本文件: {}", filepath);
}
file.close();
return true;
}
bool CustomEffectManager::saveAllToFile(const std::string &filepath) {
std::ofstream file(filepath);
if (!file.is_open()) {
E2D_ERROR("无法创建文件: {}", filepath);
return false;
}
json effectsArray = json::array();
for (const auto &[name, config] : configs_) {
effectsArray.push_back(effectConfigToJson(config));
}
file << effectsArray.dump(2);
file.close();
E2D_INFO("保存所有特效配置到: {}", filepath);
return true;
}
void CustomEffectManager::registerConfig(const std::string &name,
const CustomEffectConfig &config) {
configs_[name] = config;
E2D_INFO("注册特效配置: {}", name);
}
CustomEffectConfig *CustomEffectManager::getConfig(const std::string &name) {
auto it = configs_.find(name);
if (it != configs_.end()) {
return &it->second;
}
return nullptr;
}
void CustomEffectManager::removeConfig(const std::string &name) {
configs_.erase(name);
}
std::vector<std::string> CustomEffectManager::getConfigNames() const {
std::vector<std::string> names;
for (const auto &[name, _] : configs_) {
names.push_back(name);
}
return names;
}
Ptr<CustomEffect> CustomEffectManager::createEffect(const std::string &name) {
auto config = getConfig(name);
if (!config) {
E2D_ERROR("特效配置不存在: {}", name);
return nullptr;
}
return createEffectFromConfig(*config);
}
Ptr<CustomEffect>
CustomEffectManager::createEffectFromConfig(const CustomEffectConfig &config) {
std::string typeName;
switch (config.type) {
case CustomEffectType::Particle:
typeName = "Particle";
break;
case CustomEffectType::PostProcess:
typeName = "PostProcess";
break;
default:
typeName = "Particle";
break;
}
auto effect = E2D_CUSTOM_EFFECT_FACTORY().create(typeName, config);
if (effect && effect->init()) {
activeEffects_.push_back(effect);
return effect;
}
return nullptr;
}
void CustomEffectManager::destroyEffect(Ptr<CustomEffect> effect) {
if (!effect)
return;
effect->shutdown();
auto it = std::find(activeEffects_.begin(), activeEffects_.end(), effect);
if (it != activeEffects_.end()) {
activeEffects_.erase(it);
}
}
void CustomEffectManager::update(float dt) {
for (auto &effect : activeEffects_) {
if (effect->isPlaying()) {
effect->update(dt);
}
}
// 清理已完成的特效
activeEffects_.erase(std::remove_if(activeEffects_.begin(),
activeEffects_.end(),
[](const Ptr<CustomEffect> &effect) {
return effect->isFinished();
}),
activeEffects_.end());
}
void CustomEffectManager::render(RenderBackend &renderer) {
for (auto &effect : activeEffects_) {
if (effect->isPlaying()) {
effect->render(renderer);
}
}
}
void CustomEffectManager::stopAll() {
for (auto &effect : activeEffects_) {
effect->stop();
}
}
Ptr<CustomEffect> CustomEffectManager::play(const std::string &name,
const Vec2 &position) {
auto effect = createEffect(name);
if (effect) {
effect->setPosition(position);
effect->play();
}
return effect;
}
void CustomEffectManager::playOneShot(const std::string &name,
const Vec2 &position) {
auto effect = play(name, position);
if (effect) {
// 设置非循环,播放一次后自动销毁
effect->play();
}
}
// ============================================================================
// EffectBuilder实现
// ============================================================================
CustomEffectConfig EffectBuilder::Particle(const std::string &name) {
CustomEffectConfig config;
config.name = name;
config.type = CustomEffectType::Particle;
config.duration = -1.0f;
config.loop = true;
config.delay = 0.0f;
// 默认粒子配置
config.emitterConfig.emissionRate = 100.0f;
config.emitterConfig.minLife = 1.0f;
config.emitterConfig.maxLife = 2.0f;
config.emitterConfig.minStartSize = 10.0f;
config.emitterConfig.maxStartSize = 20.0f;
config.emitterConfig.minEndSize = 0.0f;
config.emitterConfig.maxEndSize = 5.0f;
config.emitterConfig.minVelocity = Vec2(-50.0f, -50.0f);
config.emitterConfig.maxVelocity = Vec2(50.0f, 50.0f);
config.emitterConfig.acceleration = Vec2(0.0f, 0.0f);
config.emitterConfig.startColor = Colors::White;
config.emitterConfig.endColor = Colors::Transparent;
config.emitterConfig.blendMode = BlendMode::Additive;
return config;
}
CustomEffectConfig EffectBuilder::Fire(const std::string &name) {
CustomEffectConfig config = Particle(name);
config.emitterConfig = ParticlePreset::Fire();
return config;
}
CustomEffectConfig EffectBuilder::Smoke(const std::string &name) {
CustomEffectConfig config = Particle(name);
config.emitterConfig = ParticlePreset::Smoke();
return config;
}
CustomEffectConfig EffectBuilder::Explosion(const std::string &name) {
CustomEffectConfig config = Particle(name);
config.emitterConfig = ParticlePreset::Explosion();
config.duration = 2.0f;
config.loop = false;
return config;
}
CustomEffectConfig EffectBuilder::Magic(const std::string &name) {
CustomEffectConfig config = Particle(name);
config.emitterConfig = ParticlePreset::Magic();
return config;
}
CustomEffectConfig EffectBuilder::Sparkle(const std::string &name) {
CustomEffectConfig config = Particle(name);
config.emitterConfig = ParticlePreset::Sparkle();
return config;
}
CustomEffectConfig EffectBuilder::Bloom(const std::string &name) {
CustomEffectConfig config;
config.name = name;
config.type = CustomEffectType::PostProcess;
config.duration = -1.0f;
config.loop = true;
config.shaderParams["intensity"] = 1.5f;
config.shaderParams["threshold"] = 0.8f;
return config;
}
CustomEffectConfig EffectBuilder::Blur(const std::string &name) {
CustomEffectConfig config;
config.name = name;
config.type = CustomEffectType::PostProcess;
config.duration = -1.0f;
config.loop = true;
config.shaderParams["radius"] = 2.0f;
return config;
}
CustomEffectConfig EffectBuilder::Vignette(const std::string &name) {
CustomEffectConfig config;
config.name = name;
config.type = CustomEffectType::PostProcess;
config.duration = -1.0f;
config.loop = true;
config.shaderParams["intensity"] = 0.5f;
return config;
}
CustomEffectConfig EffectBuilder::ColorGrading(const std::string &name) {
CustomEffectConfig config;
config.name = name;
config.type = CustomEffectType::PostProcess;
config.duration = -1.0f;
config.loop = true;
config.shaderParams["brightness"] = 1.0f;
config.shaderParams["contrast"] = 1.0f;
config.shaderParams["saturation"] = 1.0f;
return config;
}
} // namespace extra2d

View File

@ -1,468 +0,0 @@
#include <cmath>
#include <extra2d/effects/particle_system.h>
#include <extra2d/graphics/render_backend.h>
#include <extra2d/utils/logger.h>
namespace extra2d {
// ============================================================================
// 形状生成函数类型和查找表
// ============================================================================
using ShapeGenerator = Vec2 (ParticleEmitter::*)();
// 形状生成函数指针数组,索引对应 EmitterConfig::Shape 枚举值
static constexpr ShapeGenerator SHAPE_GENERATORS[] = {
&ParticleEmitter::randomPointShape, // Shape::Point = 0
&ParticleEmitter::randomCircleShape, // Shape::Circle = 1
&ParticleEmitter::randomRectangleShape, // Shape::Rectangle = 2
&ParticleEmitter::randomConeShape // Shape::Cone = 3
};
static constexpr size_t SHAPE_GENERATOR_COUNT = sizeof(SHAPE_GENERATORS) / sizeof(SHAPE_GENERATORS[0]);
// ============================================================================
// ParticleEmitter实现
// ============================================================================
ParticleEmitter::ParticleEmitter() : rng_(static_cast<uint32_t>(std::random_device{}())) {}
bool ParticleEmitter::init(size_t maxParticles) {
particles_.resize(maxParticles);
activeCount_ = 0;
return true;
}
void ParticleEmitter::shutdown() {
particles_.clear();
activeCount_ = 0;
}
void ParticleEmitter::start() {
emitting_ = true;
emissionTime_ = 0.0f;
}
void ParticleEmitter::stop() { emitting_ = false; }
void ParticleEmitter::burst(int count) {
for (int i = 0; i < count && activeCount_ < particles_.size(); ++i) {
emitParticle();
}
}
void ParticleEmitter::reset() {
for (auto &particle : particles_) {
particle.active = false;
}
activeCount_ = 0;
emissionTimer_ = 0.0f;
emissionTime_ = 0.0f;
}
void ParticleEmitter::update(float dt) {
// 发射新粒子
if (emitting_) {
emissionTime_ += dt;
// 检查持续时间
if (config_.emissionDuration > 0 &&
emissionTime_ >= config_.emissionDuration) {
emitting_ = false;
}
emissionTimer_ += dt;
float emissionInterval = 1.0f / config_.emissionRate;
while (emissionTimer_ >= emissionInterval &&
activeCount_ < particles_.size()) {
emitParticle();
emissionTimer_ -= emissionInterval;
}
}
// 更新活跃粒子
size_t newActiveCount = 0;
for (size_t i = 0; i < activeCount_; ++i) {
auto &p = particles_[i];
if (!p.active)
continue;
// 更新生命周期
p.life -= dt;
if (p.life <= 0.0f) {
p.active = false;
continue;
}
// 更新物理
p.velocity += p.acceleration * dt;
p.position += p.velocity * dt;
p.rotation += p.angularVelocity * dt;
// 更新大小
p.size += p.sizeDelta * dt;
if (p.size < 0.0f)
p.size = 0.0f;
// 更新颜色
p.color += p.colorDelta * dt;
// 保持活跃
if (newActiveCount != i) {
particles_[newActiveCount] = p;
}
newActiveCount++;
}
activeCount_ = newActiveCount;
}
void ParticleEmitter::render(RenderBackend &renderer) {
if (activeCount_ == 0)
return;
// 设置混合模式
renderer.setBlendMode(config_.blendMode);
// 渲染所有活跃粒子
if (config_.texture) {
// 使用纹理批量渲染
renderer.beginSpriteBatch();
for (size_t i = 0; i < activeCount_; ++i) {
const auto &p = particles_[i];
if (!p.active)
continue;
// 计算目标矩形
// 锚点由 RenderBackend 在绘制时处理,这里只传递位置和尺寸
Rect destRect(p.position.x, p.position.y, p.size, p.size);
renderer.drawSprite(
*config_.texture, destRect,
Rect(0, 0, config_.texture->getWidth(), config_.texture->getHeight()),
p.color, p.rotation, Vec2(0.5f, 0.5f));
}
renderer.endSpriteBatch();
} else {
// 没有纹理,使用圆形填充渲染
for (size_t i = 0; i < activeCount_; ++i) {
const auto &p = particles_[i];
if (!p.active)
continue;
// 渲染圆形粒子
renderer.fillCircle(p.position, p.size * 0.5f, p.color);
}
}
}
void ParticleEmitter::emitParticle() {
if (activeCount_ >= particles_.size())
return;
Particle &p = particles_[activeCount_];
p.active = true;
// 位置
p.position = position_ + randomPointInShape();
// 速度
p.velocity = randomVelocity();
// 加速度
p.acceleration = config_.acceleration;
// 旋转
p.rotation = randomFloat(config_.minRotation, config_.maxRotation);
p.angularVelocity =
randomFloat(config_.minAngularVelocity, config_.maxAngularVelocity);
// 大小
float startSize = randomFloat(config_.minStartSize, config_.maxStartSize);
float endSize = randomFloat(config_.minEndSize, config_.maxEndSize);
p.size = startSize;
// 生命周期
p.maxLife = randomFloat(config_.minLife, config_.maxLife);
p.life = p.maxLife;
// 计算每帧变化量
if (p.maxLife > 0.0f) {
p.sizeDelta = (endSize - startSize) / p.maxLife;
p.colorDelta = (config_.endColor - config_.startColor) / p.maxLife;
}
// 颜色
p.color = config_.startColor;
activeCount_++;
}
float ParticleEmitter::randomFloat(float min, float max) {
return rng_.nextFloat(min, max);
}
Vec2 ParticleEmitter::randomPointInShape() {
// 使用查找表替代 switch
size_t shapeIndex = static_cast<size_t>(config_.shape);
if (shapeIndex < SHAPE_GENERATOR_COUNT) {
return (this->*SHAPE_GENERATORS[shapeIndex])();
}
return Vec2::Zero();
}
Vec2 ParticleEmitter::randomPointShape() {
return Vec2::Zero();
}
Vec2 ParticleEmitter::randomCircleShape() {
float angle = randomFloat(0.0f, 2.0f * 3.14159265359f);
float radius = randomFloat(0.0f, config_.shapeRadius);
return Vec2(std::cos(angle) * radius, std::sin(angle) * radius);
}
Vec2 ParticleEmitter::randomRectangleShape() {
return Vec2(
randomFloat(-config_.shapeSize.x * 0.5f, config_.shapeSize.x * 0.5f),
randomFloat(-config_.shapeSize.y * 0.5f, config_.shapeSize.y * 0.5f));
}
Vec2 ParticleEmitter::randomConeShape() {
float angle =
randomFloat(-config_.coneAngle * 0.5f, config_.coneAngle * 0.5f);
float radius = randomFloat(0.0f, config_.shapeRadius);
float rad = angle * 3.14159265359f / 180.0f;
return Vec2(std::cos(rad) * radius, std::sin(rad) * radius);
}
Vec2 ParticleEmitter::randomVelocity() {
return Vec2(randomFloat(config_.minVelocity.x, config_.maxVelocity.x),
randomFloat(config_.minVelocity.y, config_.maxVelocity.y));
}
// ============================================================================
// ParticleSystem实现
// ============================================================================
ParticleSystem::ParticleSystem() {}
Ptr<ParticleSystem> ParticleSystem::create() {
return std::make_shared<ParticleSystem>();
}
Ptr<ParticleEmitter> ParticleSystem::addEmitter(const EmitterConfig &config) {
auto emitter = std::make_shared<ParticleEmitter>();
emitter->setConfig(config);
emitter->init(1000); // 默认最大1000个粒子
emitters_.push_back(emitter);
return emitter;
}
void ParticleSystem::removeEmitter(Ptr<ParticleEmitter> emitter) {
auto it = std::find(emitters_.begin(), emitters_.end(), emitter);
if (it != emitters_.end()) {
(*it)->shutdown();
emitters_.erase(it);
}
}
void ParticleSystem::removeAllEmitters() {
for (auto &emitter : emitters_) {
emitter->shutdown();
}
emitters_.clear();
}
void ParticleSystem::startAll() {
for (auto &emitter : emitters_) {
emitter->start();
}
}
void ParticleSystem::stopAll() {
for (auto &emitter : emitters_) {
emitter->stop();
}
}
void ParticleSystem::resetAll() {
for (auto &emitter : emitters_) {
emitter->reset();
}
}
void ParticleSystem::onUpdate(float dt) {
// 获取粒子系统的世界位置
auto worldPos = convertToWorldSpace(Vec2::Zero());
for (auto &emitter : emitters_) {
// 更新发射器位置为粒子系统的世界位置
emitter->setPosition(worldPos);
// 更新发射器
emitter->update(dt);
}
}
void ParticleSystem::onDraw(RenderBackend &renderer) {
for (auto &emitter : emitters_) {
emitter->render(renderer);
}
}
// ============================================================================
// 预设实现
// ============================================================================
EmitterConfig ParticlePreset::Fire() {
EmitterConfig config;
config.emissionRate = 200.0f;
config.minLife = 0.5f;
config.maxLife = 1.5f;
config.minStartSize = 20.0f;
config.maxStartSize = 40.0f;
config.minEndSize = 5.0f;
config.maxEndSize = 10.0f;
config.minVelocity = Vec2(-30.0f, -150.0f); // 向上负y
config.maxVelocity = Vec2(30.0f, -50.0f);
config.acceleration = Vec2(0.0f, 0.0f);
config.startColor = Color(1.0f, 0.8f, 0.2f, 1.0f); // 黄色
config.endColor = Color(1.0f, 0.2f, 0.0f, 0.0f); // 红色透明
config.blendMode = BlendMode::Additive;
return config;
}
EmitterConfig ParticlePreset::Smoke() {
EmitterConfig config;
config.emissionRate = 50.0f;
config.minLife = 2.0f;
config.maxLife = 4.0f;
config.minStartSize = 30.0f;
config.maxStartSize = 60.0f;
config.minEndSize = 80.0f;
config.maxEndSize = 120.0f;
config.minVelocity = Vec2(-20.0f, -60.0f); // 向上负y
config.maxVelocity = Vec2(20.0f, -30.0f);
config.acceleration = Vec2(0.0f, -10.0f); // 向上加速度
config.startColor = Color(0.5f, 0.5f, 0.5f, 0.5f); // 灰色半透明
config.endColor = Color(0.3f, 0.3f, 0.3f, 0.0f); // 深灰透明
config.blendMode = BlendMode::Alpha;
return config;
}
EmitterConfig ParticlePreset::Explosion() {
EmitterConfig config;
config.emissionRate = 1000.0f;
config.emissionDuration = 0.1f; // 瞬间爆发
config.minLife = 0.5f;
config.maxLife = 1.5f;
config.minStartSize = 10.0f;
config.maxStartSize = 30.0f;
config.minEndSize = 0.0f;
config.maxEndSize = 5.0f;
config.minVelocity = Vec2(-300.0f, -300.0f);
config.maxVelocity = Vec2(300.0f, 300.0f);
config.acceleration = Vec2(0.0f, -50.0f);
config.startColor = Color(1.0f, 1.0f, 0.5f, 1.0f); // 亮黄
config.endColor = Color(1.0f, 0.3f, 0.0f, 0.0f); // 橙红透明
config.blendMode = BlendMode::Additive;
return config;
}
EmitterConfig ParticlePreset::Sparkle() {
EmitterConfig config;
config.emissionRate = 20.0f;
config.minLife = 0.2f;
config.maxLife = 0.8f;
config.minStartSize = 2.0f;
config.maxStartSize = 5.0f;
config.minEndSize = 0.0f;
config.maxEndSize = 2.0f;
config.minVelocity = Vec2(-10.0f, -10.0f);
config.maxVelocity = Vec2(10.0f, 10.0f);
config.acceleration = Vec2(0.0f, 0.0f);
config.startColor = Color(1.0f, 1.0f, 1.0f, 1.0f); // 白色
config.endColor = Color(1.0f, 1.0f, 1.0f, 0.0f); // 透明
config.blendMode = BlendMode::Additive;
return config;
}
EmitterConfig ParticlePreset::Rain() {
EmitterConfig config;
config.emissionRate = 500.0f;
config.minLife = 1.0f;
config.maxLife = 2.0f;
config.minStartSize = 2.0f;
config.maxStartSize = 4.0f;
config.minEndSize = 2.0f;
config.maxEndSize = 4.0f;
config.minVelocity = Vec2(-100.0f, -400.0f);
config.maxVelocity = Vec2(100.0f, -600.0f);
config.acceleration = Vec2(0.0f, -100.0f);
config.startColor = Color(0.7f, 0.8f, 1.0f, 0.6f); // 淡蓝
config.endColor = Color(0.7f, 0.8f, 1.0f, 0.3f); // 淡蓝半透明
config.shape = EmitterConfig::Shape::Rectangle;
config.shapeSize = Vec2(800.0f, 100.0f); // 在顶部区域发射
config.blendMode = BlendMode::Alpha;
return config;
}
EmitterConfig ParticlePreset::Snow() {
EmitterConfig config;
config.emissionRate = 100.0f;
config.minLife = 3.0f;
config.maxLife = 6.0f;
config.minStartSize = 5.0f;
config.maxStartSize = 10.0f;
config.minEndSize = 5.0f;
config.maxEndSize = 10.0f;
config.minVelocity = Vec2(-30.0f, -30.0f);
config.maxVelocity = Vec2(30.0f, -80.0f);
config.acceleration = Vec2(0.0f, 0.0f);
config.startColor = Color(1.0f, 1.0f, 1.0f, 0.8f); // 白色
config.endColor = Color(1.0f, 1.0f, 1.0f, 0.8f); // 白色
config.shape = EmitterConfig::Shape::Rectangle;
config.shapeSize = Vec2(800.0f, 100.0f);
config.blendMode = BlendMode::Alpha;
return config;
}
EmitterConfig ParticlePreset::Magic() {
EmitterConfig config;
config.emissionRate = 100.0f;
config.minLife = 1.0f;
config.maxLife = 2.0f;
config.minStartSize = 5.0f;
config.maxStartSize = 15.0f;
config.minEndSize = 0.0f;
config.maxEndSize = 5.0f;
config.minVelocity = Vec2(-50.0f, -50.0f); // 主要向上
config.maxVelocity = Vec2(50.0f, -50.0f);
config.acceleration = Vec2(0.0f, -20.0f); // 向上加速度
config.startColor = Color(0.5f, 0.2f, 1.0f, 1.0f); // 紫色
config.endColor = Color(0.2f, 0.8f, 1.0f, 0.0f); // 青色透明
config.blendMode = BlendMode::Additive;
return config;
}
EmitterConfig ParticlePreset::Bubbles() {
EmitterConfig config;
config.emissionRate = 30.0f;
config.minLife = 2.0f;
config.maxLife = 4.0f;
config.minStartSize = 5.0f;
config.maxStartSize = 15.0f;
config.minEndSize = 5.0f;
config.maxEndSize = 15.0f;
config.minVelocity = Vec2(-20.0f, 20.0f);
config.maxVelocity = Vec2(20.0f, 60.0f);
config.acceleration = Vec2(0.0f, 30.0f);
config.startColor = Color(0.8f, 0.9f, 1.0f, 0.4f); // 淡蓝透明
config.endColor = Color(0.8f, 0.9f, 1.0f, 0.1f); // 更透明
config.blendMode = BlendMode::Alpha;
return config;
}
} // namespace extra2d

View File

@ -1,389 +0,0 @@
#include <extra2d/effects/post_process.h>
#include <extra2d/graphics/render_backend.h>
#include <extra2d/graphics/render_target.h>
#include <extra2d/utils/logger.h>
#include <glad/glad.h>
namespace extra2d {
// ============================================================================
// 静态成员初始化
// ============================================================================
GLuint PostProcessEffect::quadVao_ = 0;
GLuint PostProcessEffect::quadVbo_ = 0;
bool PostProcessEffect::quadInitialized_ = false;
// ============================================================================
// PostProcessEffect实现
// ============================================================================
PostProcessEffect::PostProcessEffect(const std::string &name) : name_(name) {}
bool PostProcessEffect::init() {
initQuad();
valid_ = true;
return true;
}
void PostProcessEffect::shutdown() {
shader_.reset();
valid_ = false;
}
void PostProcessEffect::apply(const Texture &source, RenderTarget &target,
RenderBackend &renderer) {
if (!enabled_ || !valid_)
return;
target.bind();
if (shader_) {
shader_->bind();
shader_->setInt("u_texture", 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D,
static_cast<GLuint>(
reinterpret_cast<uintptr_t>(source.getNativeHandle())));
onShaderBind(*shader_);
}
renderFullscreenQuad();
if (shader_) {
shader_->unbind();
}
target.unbind();
}
bool PostProcessEffect::loadShader(const std::string &vertSource,
const std::string &fragSource) {
shader_ = std::make_shared<GLShader>();
if (!shader_->compileFromSource(vertSource.c_str(), fragSource.c_str())) {
E2D_ERROR("后处理效果 '{}' 加载Shader失败", name_);
shader_.reset();
return false;
}
return true;
}
bool PostProcessEffect::loadShaderFromFile(const std::string &vertPath,
const std::string &fragPath) {
shader_ = std::make_shared<GLShader>();
if (!shader_->compileFromFile(vertPath, fragPath)) {
E2D_ERROR("后处理效果 '{}' 从文件加载Shader失败", name_);
shader_.reset();
return false;
}
return true;
}
void PostProcessEffect::initQuad() {
if (quadInitialized_)
return;
// 全屏四边形顶点数据(位置和纹理坐标)
float quadVertices[] = {// 位置 // 纹理坐标
-1.0f, 1.0f, 0.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 1.0f, 0.0f, 1.0f,
1.0f, -1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f};
glGenVertexArrays(1, &quadVao_);
glGenBuffers(1, &quadVbo_);
glBindVertexArray(quadVao_);
glBindBuffer(GL_ARRAY_BUFFER, quadVbo_);
glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), quadVertices,
GL_STATIC_DRAW);
// 位置属性
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void *)0);
// 纹理坐标属性
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float),
(void *)(2 * sizeof(float)));
glBindVertexArray(0);
quadInitialized_ = true;
}
void PostProcessEffect::destroyQuad() {
if (quadVao_ != 0) {
glDeleteVertexArrays(1, &quadVao_);
quadVao_ = 0;
}
if (quadVbo_ != 0) {
glDeleteBuffers(1, &quadVbo_);
quadVbo_ = 0;
}
quadInitialized_ = false;
}
void PostProcessEffect::renderFullscreenQuad() {
if (!quadInitialized_) {
initQuad();
}
glBindVertexArray(quadVao_);
glDrawArrays(GL_TRIANGLES, 0, 6);
glBindVertexArray(0);
}
// ============================================================================
// PostProcessStack实现
// ============================================================================
PostProcessStack::PostProcessStack() = default;
PostProcessStack::~PostProcessStack() { shutdown(); }
bool PostProcessStack::init(int width, int height) {
E2D_INFO("初始化后处理栈...");
width_ = width;
height_ = height;
// 创建两个渲染目标用于乒乓渲染
RenderTargetConfig config;
config.width = width;
config.height = height;
config.hasDepthBuffer = false;
config.autoResize = false;
renderTargetA_ = std::make_shared<RenderTarget>();
renderTargetB_ = std::make_shared<RenderTarget>();
if (!renderTargetA_->init(config)) {
E2D_ERROR("创建后处理渲染目标A失败");
return false;
}
if (!renderTargetB_->init(config)) {
E2D_ERROR("创建后处理渲染目标B失败");
return false;
}
valid_ = true;
E2D_INFO("后处理栈初始化成功");
return true;
}
void PostProcessStack::shutdown() {
E2D_INFO("关闭后处理栈...");
clearEffects();
if (renderTargetA_) {
renderTargetA_->shutdown();
renderTargetA_.reset();
}
if (renderTargetB_) {
renderTargetB_->shutdown();
renderTargetB_.reset();
}
valid_ = false;
}
void PostProcessStack::addEffect(Ptr<PostProcessEffect> effect) {
if (effect && effect->init()) {
effects_.push_back(effect);
E2D_INFO("添加后处理效果: {}", effect->getName());
}
}
void PostProcessStack::insertEffect(size_t index,
Ptr<PostProcessEffect> effect) {
if (effect && effect->init() && index <= effects_.size()) {
effects_.insert(effects_.begin() + index, effect);
E2D_INFO("插入后处理效果 '{}' 到位置 {}", effect->getName(), index);
}
}
void PostProcessStack::removeEffect(const std::string &name) {
for (auto it = effects_.begin(); it != effects_.end(); ++it) {
if ((*it)->getName() == name) {
(*it)->shutdown();
effects_.erase(it);
E2D_INFO("移除后处理效果: {}", name);
return;
}
}
}
void PostProcessStack::removeEffect(size_t index) {
if (index < effects_.size()) {
effects_[index]->shutdown();
effects_.erase(effects_.begin() + index);
}
}
Ptr<PostProcessEffect> PostProcessStack::getEffect(const std::string &name) {
for (auto &effect : effects_) {
if (effect->getName() == name) {
return effect;
}
}
return nullptr;
}
Ptr<PostProcessEffect> PostProcessStack::getEffect(size_t index) {
if (index < effects_.size()) {
return effects_[index];
}
return nullptr;
}
void PostProcessStack::clearEffects() {
for (auto &effect : effects_) {
effect->shutdown();
}
effects_.clear();
}
void PostProcessStack::beginCapture() {
if (!valid_)
return;
renderTargetA_->bind();
renderTargetA_->clear(Colors::Black);
capturing_ = true;
}
void PostProcessStack::endCapture(RenderBackend &renderer) {
if (!valid_ || !capturing_)
return;
renderTargetA_->unbind();
// 应用所有后处理效果
if (effects_.empty()) {
// 没有效果,直接渲染到屏幕
// 这里需要渲染renderTargetA_的纹理到屏幕
capturing_ = false;
return;
}
// 乒乓渲染
RenderTarget *readTarget = renderTargetA_.get();
RenderTarget *writeTarget = renderTargetB_.get();
for (size_t i = 0; i < effects_.size(); ++i) {
auto &effect = effects_[i];
if (effect->isEnabled()) {
effect->apply(*readTarget->getColorTexture(), *writeTarget, renderer);
}
// 交换读写目标
std::swap(readTarget, writeTarget);
}
// 最终结果在readTarget中因为最后一次交换
// 这里应该将结果渲染到屏幕
capturing_ = false;
}
void PostProcessStack::process(const Texture &source, RenderTarget &target,
RenderBackend &renderer) {
if (!valid_)
return;
RenderTarget *readTarget = nullptr;
RenderTarget *writeTarget = nullptr;
// 确定读写目标
if (target.getFBO() == renderTargetA_->getFBO()) {
readTarget = renderTargetB_.get();
writeTarget = renderTargetA_.get();
} else {
readTarget = renderTargetA_.get();
writeTarget = renderTargetB_.get();
}
// 首先将源纹理复制到读目标
readTarget->bind();
// 这里需要渲染源纹理到readTarget
readTarget->unbind();
// 应用效果
for (auto &effect : effects_) {
if (effect->isEnabled()) {
effect->apply(*readTarget->getColorTexture(), *writeTarget, renderer);
}
std::swap(readTarget, writeTarget);
}
// 将最终结果复制到目标
readTarget->blitTo(target, true, false);
}
void PostProcessStack::resize(int width, int height) {
if (width_ == width && height_ == height)
return;
width_ = width;
height_ = height;
if (renderTargetA_) {
renderTargetA_->resize(width, height);
}
if (renderTargetB_) {
renderTargetB_->resize(width, height);
}
}
// ============================================================================
// PostProcessManager实现
// ============================================================================
PostProcessManager &PostProcessManager::getInstance() {
static PostProcessManager instance;
return instance;
}
void PostProcessManager::init(int width, int height) {
if (initialized_)
return;
E2D_INFO("初始化后处理管理器...");
mainStack_.init(width, height);
initialized_ = true;
}
void PostProcessManager::shutdown() {
if (!initialized_)
return;
E2D_INFO("关闭后处理管理器...");
mainStack_.shutdown();
initialized_ = false;
}
void PostProcessManager::resize(int width, int height) {
if (initialized_) {
mainStack_.resize(width, height);
}
}
void PostProcessManager::beginFrame() {
if (initialized_) {
mainStack_.beginCapture();
}
}
void PostProcessManager::endFrame(RenderBackend &renderer) {
if (initialized_) {
mainStack_.endCapture(renderer);
}
}
} // namespace extra2d

View File

@ -1,7 +1,5 @@
#include <algorithm>
#include <cmath>
#include <extra2d/action/action.h>
#include <extra2d/action/action_manager.h>
#include <extra2d/graphics/render_command.h>
#include <extra2d/scene/node.h>
#include <extra2d/scene/scene.h>
@ -13,8 +11,6 @@ Node::Node() = default;
Node::~Node() {
removeAllChildren();
stopAllActions();
ActionManager::getInstance()->removeAllActionsFromTarget(this);
}
void Node::addChild(Ptr<Node> child) {
@ -403,46 +399,6 @@ void Node::updateSpatialIndex() {
}
}
// ============================================================================
// 动作系统 - 新接口
// ============================================================================
Action* Node::runAction(Action* action) {
if (!action) {
return nullptr;
}
ActionManager::getInstance()->addAction(action, this);
return action;
}
void Node::stopAllActions() {
ActionManager::getInstance()->removeAllActionsFromTarget(this);
}
void Node::stopAction(Action* action) {
ActionManager::getInstance()->removeAction(action);
}
void Node::stopActionByTag(int tag) {
ActionManager::getInstance()->removeActionByTag(tag, this);
}
void Node::stopActionsByFlags(unsigned int flags) {
ActionManager::getInstance()->removeActionsByFlags(flags, this);
}
Action* Node::getActionByTag(int tag) {
return ActionManager::getInstance()->getActionByTag(tag, this);
}
size_t Node::getActionCount() const {
return ActionManager::getInstance()->getActionCount(const_cast<Node*>(this));
}
bool Node::isRunningActions() const {
return getActionCount() > 0;
}
void Node::update(float dt) { onUpdate(dt); }
void Node::render(RenderBackend &renderer) {

View File

@ -1,279 +0,0 @@
#include <cmath>
#include <extra2d/extra2d.h>
using namespace extra2d;
// ============================================================================
// 碰撞测试节点 - 有实际边界框
// ============================================================================
class CollisionBox : public Node {
public:
CollisionBox(float width, float height, const Color &color)
: width_(width), height_(height), color_(color), isColliding_(false) {
// 启用空间索引,这是碰撞检测的关键
setSpatialIndexed(true);
}
void setColliding(bool colliding) { isColliding_ = colliding; }
Rect getBoundingBox() const override {
// 返回实际的矩形边界
Vec2 pos = getPosition();
return Rect(pos.x - width_ / 2, pos.y - height_ / 2, width_, height_);
}
void onRender(RenderBackend &renderer) override {
Vec2 pos = getPosition();
// 绘制填充矩形
Color fillColor = isColliding_ ? Color(1.0f, 0.2f, 0.2f, 0.8f) : color_;
renderer.fillRect(
Rect(pos.x - width_ / 2, pos.y - height_ / 2, width_, height_),
fillColor);
// 绘制边框
Color borderColor = isColliding_ ? Color(1.0f, 0.0f, 0.0f, 1.0f)
: Color(1.0f, 1.0f, 1.0f, 0.5f);
float borderWidth = isColliding_ ? 3.0f : 2.0f;
renderer.drawRect(
Rect(pos.x - width_ / 2, pos.y - height_ / 2, width_, height_),
borderColor, borderWidth);
}
private:
float width_, height_;
Color color_;
bool isColliding_;
};
// ============================================================================
// 碰撞检测场景
// ============================================================================
class CollisionDemoScene : public Scene {
public:
void onEnter() override {
E2D_LOG_INFO("CollisionDemoScene::onEnter - 碰撞检测演示");
// 设置背景色
setBackgroundColor(Color(0.05f, 0.05f, 0.1f, 1.0f));
// 获取屏幕中心
auto &app = Application::instance();
float centerX = app.getConfig().width / 2.0f;
float centerY = app.getConfig().height / 2.0f;
// 创建静态碰撞框
createStaticBoxes(centerX, centerY);
// 创建移动的中心方块
centerBox_ =
makePtr<CollisionBox>(80.0f, 80.0f, Color(0.2f, 0.6f, 1.0f, 0.8f));
centerBox_->setPosition(Vec2(centerX, centerY));
addChild(centerBox_);
// 加载字体并创建UI
loadFonts();
E2D_LOG_INFO("创建了 {} 个碰撞框", boxes_.size() + 1);
}
void onUpdate(float dt) override {
Scene::onUpdate(dt);
// 旋转中心方块
rotationAngle_ += rotationSpeed_ * dt;
if (rotationAngle_ >= 360.0f)
rotationAngle_ -= 360.0f;
// 让中心方块沿圆形路径移动
float radius = 150.0f;
float rad = rotationAngle_ * 3.14159f / 180.0f;
auto &app = Application::instance();
Vec2 center =
Vec2(app.getConfig().width / 2.0f, app.getConfig().height / 2.0f);
centerBox_->setPosition(Vec2(center.x + std::cos(rad) * radius,
center.y + std::sin(rad) * radius));
centerBox_->setRotation(rotationAngle_);
// 执行碰撞检测
performCollisionDetection();
// 更新UI文本
updateUI();
// 检查退出按键
auto &input = Application::instance().input();
if (input.isButtonPressed(GamepadButton::Start)) {
E2D_LOG_INFO("退出应用");
Application::instance().quit();
}
}
private:
/**
* @brief UI文本
*/
void loadFonts() {
auto &resources = Application::instance().resources();
titleFont_ = resources.loadFont("assets/font.ttf", 60, true);
infoFont_ = resources.loadFont("assets/font.ttf", 28, true);
// 创建标题文本
titleText_ = Text::create("碰撞检测演示", titleFont_);
titleText_->setPosition(50.0f, 30.0f);
titleText_->setTextColor(Color(1.0f, 1.0f, 1.0f, 1.0f));
addChild(titleText_);
// 创建说明文本
descText_ = Text::create("蓝色方块旋转并检测碰撞", infoFont_);
descText_->setPosition(50.0f, 80.0f);
descText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f));
addChild(descText_);
collideHintText_ = Text::create("红色 = 检测到碰撞", infoFont_);
collideHintText_->setPosition(50.0f, 105.0f);
collideHintText_->setTextColor(Color(1.0f, 0.5f, 0.5f, 1.0f));
addChild(collideHintText_);
// 创建动态统计文本
collisionText_ = Text::create("", infoFont_);
collisionText_->setPosition(50.0f, 150.0f);
collisionText_->setTextColor(Color(1.0f, 1.0f, 0.5f, 1.0f));
addChild(collisionText_);
fpsText_ = Text::create("", infoFont_);
fpsText_->setPosition(50.0f, 175.0f);
fpsText_->setTextColor(Color(0.8f, 1.0f, 0.8f, 1.0f));
addChild(fpsText_);
// 创建退出提示文本
float screenHeight = static_cast<float>(Application::instance().getConfig().height);
exitHintText_ = Text::create("按 + 键退出", infoFont_);
exitHintText_->setPosition(50.0f, screenHeight - 50.0f);
exitHintText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f));
addChild(exitHintText_);
}
/**
* @brief UI文本
*/
void updateUI() {
auto &app = Application::instance();
// 使用 setFormat 更新动态文本
collisionText_->setFormat("碰撞数: %zu", collisionCount_);
fpsText_->setFormat("FPS: %u", app.fps());
}
/**
* @brief
*/
void createStaticBoxes(float centerX, float centerY) {
// 创建围绕中心的静态碰撞框
std::vector<std::pair<Vec2, Color>> positions = {
{Vec2(centerX - 200, centerY - 150), Color(0.3f, 1.0f, 0.3f, 0.7f)},
{Vec2(centerX + 200, centerY - 150), Color(1.0f, 0.3f, 0.3f, 0.7f)},
{Vec2(centerX - 200, centerY + 150), Color(0.3f, 0.3f, 1.0f, 0.7f)},
{Vec2(centerX + 200, centerY + 150), Color(1.0f, 1.0f, 0.3f, 0.7f)},
{Vec2(centerX, centerY - 220), Color(1.0f, 0.3f, 1.0f, 0.7f)},
{Vec2(centerX, centerY + 220), Color(0.3f, 1.0f, 1.0f, 0.7f)},
};
for (const auto &[pos, color] : positions) {
auto box = makePtr<CollisionBox>(70.0f, 70.0f, color);
box->setPosition(pos);
addChild(box);
boxes_.push_back(box);
}
}
/**
* @brief
*/
void performCollisionDetection() {
// 清除之前的碰撞状态
centerBox_->setColliding(false);
for (auto &box : boxes_) {
box->setColliding(false);
}
// 使用空间索引进行碰撞检测
auto collisions = queryCollisions();
collisionCount_ = collisions.size();
// 标记碰撞的节点
for (const auto &[nodeA, nodeB] : collisions) {
if (auto boxA = dynamic_cast<CollisionBox *>(nodeA)) {
boxA->setColliding(true);
}
if (auto boxB = dynamic_cast<CollisionBox *>(nodeB)) {
boxB->setColliding(true);
}
}
}
Ptr<CollisionBox> centerBox_;
std::vector<Ptr<CollisionBox>> boxes_;
float rotationAngle_ = 0.0f;
float rotationSpeed_ = 60.0f; // 旋转速度(度/秒)
size_t collisionCount_ = 0;
// 字体资源
Ptr<FontAtlas> titleFont_;
Ptr<FontAtlas> infoFont_;
// UI 文本组件
Ptr<Text> titleText_;
Ptr<Text> descText_;
Ptr<Text> collideHintText_;
Ptr<Text> collisionText_;
Ptr<Text> fpsText_;
Ptr<Text> exitHintText_;
};
// ============================================================================
// 程序入口
// ============================================================================
int main(int argc, char **argv)
{
// 初始化日志系统
Logger::init();
Logger::setLevel(LogLevel::Debug);
E2D_LOG_INFO("========================");
E2D_LOG_INFO("Easy2D 碰撞检测演示");
E2D_LOG_INFO("========================");
// 获取应用实例
auto &app = Application::instance();
// 配置应用
AppConfig config;
config.title = "Easy2D - 碰撞检测演示";
config.width = 1280;
config.height = 720;
config.vsync = true;
config.fpsLimit = 60;
// 初始化应用
if (!app.init(config)) {
E2D_LOG_ERROR("应用初始化失败!");
return -1;
}
// 进入场景
app.enterScene(makePtr<CollisionDemoScene>());
E2D_LOG_INFO("开始主循环...");
// 运行应用
app.run();
E2D_LOG_INFO("应用结束");
return 0;
}

View File

@ -1,76 +0,0 @@
-- ==============================================
-- Collision Demo 示例 - Xmake 构建脚本
-- 支持平台: MinGW (Windows), Nintendo Switch
-- ==============================================
-- 获取当前脚本所在目录(示例根目录)
local example_dir = os.scriptdir()
-- 可执行文件目标
target("collision_demo")
set_kind("binary")
add_files("main.cpp")
add_includedirs("../../Extra2D/include")
add_deps("extra2d")
-- 使用与主项目相同的平台配置
if is_plat("switch") then
set_targetdir("../../build/examples/collision_demo")
-- 构建后生成 NRO 文件
after_build(function (target)
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
local elf_file = target:targetfile()
local output_dir = path.directory(elf_file)
local nacp_file = path.join(output_dir, "collision_demo.nacp")
local nro_file = path.join(output_dir, "collision_demo.nro")
local nacptool = path.join(devkitPro, "tools/bin/nacptool.exe")
local elf2nro = path.join(devkitPro, "tools/bin/elf2nro.exe")
if os.isfile(nacptool) and os.isfile(elf2nro) then
os.vrunv(nacptool, {"--create", "Collision Demo", "Extra2D Team", "1.0.0", nacp_file})
local romfs = path.join(example_dir, "romfs")
if os.isdir(romfs) then
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file, "--romfsdir=" .. romfs})
else
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file})
end
print("Generated NRO: " .. nro_file)
end
end)
-- 打包时将 NRO 文件复制到 package 目录
after_package(function (target)
local nro_file = path.join(target:targetdir(), "collision_demo.nro")
local package_dir = target:packagedir()
if os.isfile(nro_file) and package_dir then
os.cp(nro_file, package_dir)
print("Copied NRO to package: " .. package_dir)
end
end)
elseif is_plat("mingw") then
set_targetdir("../../build/examples/collision_demo")
add_ldflags("-mwindows", {force = true})
-- 复制资源到输出目录
after_build(function (target)
local romfs = path.join(example_dir, "romfs")
if os.isdir(romfs) then
local target_dir = path.directory(target:targetfile())
local assets_dir = path.join(target_dir, "assets")
-- 创建 assets 目录
if not os.isdir(assets_dir) then
os.mkdir(assets_dir)
end
-- 复制所有资源文件(包括子目录)
os.cp(path.join(romfs, "assets/**"), assets_dir)
print("Copied assets from " .. romfs .. " to " .. assets_dir)
else
print("Warning: romfs directory not found at " .. romfs)
end
end)
end
target_end()

View File

@ -1,127 +0,0 @@
// ============================================================================
// BaseScene.cpp - Flappy Bird 基础场景实现
// ============================================================================
#include "BaseScene.h"
#include <extra2d/scene/transition_scene.h>
#include <extra2d/utils/logger.h>
namespace flappybird {
BaseScene::BaseScene() {
// 设置背景颜色为黑色(窗口四周会显示这个颜色)
setBackgroundColor(extra2d::Color(0.0f, 0.0f, 0.0f, 1.0f));
}
void BaseScene::onEnter() {
extra2d::Scene::onEnter();
// 计算并更新视口
updateViewport();
}
void BaseScene::updateViewport() {
auto &app = extra2d::Application::instance();
float windowWidth = static_cast<float>(app.window().getWidth());
float windowHeight = static_cast<float>(app.window().getHeight());
// 计算游戏内容在窗口中的居中位置
// 保持游戏原始宽高比,进行"黑边"适配
float scaleX = windowWidth / GAME_WIDTH;
float scaleY = windowHeight / GAME_HEIGHT;
// 使用较小的缩放比例,确保游戏内容完整显示在窗口中
float scale = std::min(scaleX, scaleY);
scaledGameWidth_ = GAME_WIDTH * scale;
scaledGameHeight_ = GAME_HEIGHT * scale;
// 计算居中偏移,使游戏内容在窗口中水平和垂直居中
viewportOffsetX_ = (windowWidth - scaledGameWidth_) * 0.5f;
viewportOffsetY_ = (windowHeight - scaledGameHeight_) * 0.5f;
// 设置视口大小为游戏逻辑分辨率
setViewportSize(GAME_WIDTH, GAME_HEIGHT);
// 创建并设置相机
auto camera = extra2d::makePtr<extra2d::Camera>();
// 设置正交投影,覆盖整个游戏逻辑区域
// 注意对于2D游戏Y轴向下增长所以bottom > top
camera->setViewport(0.0f, GAME_WIDTH, GAME_HEIGHT, 0.0f);
setCamera(camera);
}
void BaseScene::onRender(extra2d::RenderBackend &renderer) {
// 检查窗口大小是否改变,如果改变则更新视口
auto &app = extra2d::Application::instance();
float currentWindowWidth = static_cast<float>(app.window().getWidth());
float currentWindowHeight = static_cast<float>(app.window().getHeight());
// 如果窗口大小改变,重新计算视口
float expectedWidth = scaledGameWidth_ + viewportOffsetX_ * 2.0f;
float expectedHeight = scaledGameHeight_ + viewportOffsetY_ * 2.0f;
if (std::abs(currentWindowWidth - expectedWidth) > 1.0f ||
std::abs(currentWindowHeight - expectedHeight) > 1.0f) {
E2D_LOG_INFO("BaseScene::onRender - window size changed from ({} x {}) to "
"({} x {}), updating viewport",
expectedWidth, expectedHeight, currentWindowWidth,
currentWindowHeight);
updateViewport();
}
// 设置视口为居中区域
renderer.setViewport(
static_cast<int>(viewportOffsetX_), static_cast<int>(viewportOffsetY_),
static_cast<int>(scaledGameWidth_), static_cast<int>(scaledGameHeight_));
// 调用父类的 onRender 进行实际渲染
extra2d::Scene::onRender(renderer);
}
void BaseScene::renderContent(extra2d::RenderBackend &renderer) {
// 如果视口参数未初始化onEnter 还没被调用),先初始化
if (scaledGameWidth_ <= 0.0f || scaledGameHeight_ <= 0.0f) {
updateViewport();
}
// 检查窗口大小是否改变
auto &app = extra2d::Application::instance();
float currentWindowWidth = static_cast<float>(app.window().getWidth());
float currentWindowHeight = static_cast<float>(app.window().getHeight());
float expectedWidth = scaledGameWidth_ + viewportOffsetX_ * 2.0f;
float expectedHeight = scaledGameHeight_ + viewportOffsetY_ * 2.0f;
if (std::abs(currentWindowWidth - expectedWidth) > 1.0f ||
std::abs(currentWindowHeight - expectedHeight) > 1.0f) {
updateViewport();
}
// 检查当前场景是否作为 TransitionScene 的子场景被渲染
bool isChildOfTransition = false;
if (auto parent = getParent()) {
if (dynamic_cast<extra2d::TransitionScene *>(parent.get())) {
isChildOfTransition = true;
}
}
if (isChildOfTransition) {
// 作为 TransitionScene 的子场景时,需要设置正确的投影矩阵
// 使用游戏逻辑分辨率作为投影区域,让 TransitionScene 控制整体视口
auto camera = getActiveCamera();
if (camera) {
// 设置投影矩阵覆盖整个游戏逻辑区域
renderer.setViewProjection(camera->getViewProjectionMatrix());
}
// 渲染场景内容(投影矩阵已设置,直接渲染)
batchUpdateTransforms();
renderer.beginSpriteBatch();
render(renderer);
renderer.endSpriteBatch();
} else {
// 正常渲染时,调用父类的 renderContent 处理视口和投影
renderer.setViewport(static_cast<int>(viewportOffsetX_),
static_cast<int>(viewportOffsetY_),
static_cast<int>(scaledGameWidth_),
static_cast<int>(scaledGameHeight_));
extra2d::Scene::renderContent(renderer);
}
}
} // namespace flappybird

View File

@ -1,57 +0,0 @@
// ============================================================================
// BaseScene.h - Flappy Bird 基础场景类
// 描述: 提供统一的居中视口适配功能,所有游戏场景都应继承此类
// ============================================================================
#pragma once
#include <extra2d/extra2d.h>
namespace flappybird {
// 游戏逻辑分辨率(原始 Flappy Bird 尺寸)
static constexpr float GAME_WIDTH = 288.0f;
static constexpr float GAME_HEIGHT = 512.0f;
/**
* @brief Flappy Bird
*
*/
class BaseScene : public extra2d::Scene {
public:
/**
* @brief
*/
BaseScene();
/**
* @brief
*/
void onEnter() override;
/**
* @brief
* @param renderer
*/
void onRender(extra2d::RenderBackend &renderer) override;
/**
* @brief
* @param renderer
*/
void renderContent(extra2d::RenderBackend &renderer) override;
protected:
/**
* @brief 使
*/
void updateViewport();
// 视口适配参数(用于在窗口中居中显示游戏内容)
float scaledGameWidth_ = 0.0f; // 缩放后的游戏宽度
float scaledGameHeight_ = 0.0f; // 缩放后的游戏高度
float viewportOffsetX_ = 0.0f; // 视口水平偏移
float viewportOffsetY_ = 0.0f; // 视口垂直偏移
};
} // namespace flappybird

View File

@ -1,202 +0,0 @@
// ============================================================================
// GameOverLayer.cpp - 游戏结束层实现
// ============================================================================
#include "GameOverLayer.h"
#include "BaseScene.h"
#include "GameScene.h"
#include "Number.h"
#include "ResLoader.h"
#include "StartScene.h"
namespace flappybird {
GameOverLayer::GameOverLayer(int score) : score_(score) {
// 注意:不要在构造函数中创建子节点
// 因为此时 weak_from_this() 还不能使用
}
void GameOverLayer::onEnter() {
Node::onEnter();
// 在 onEnter 中初始化,此时 weak_from_this() 可用
// 使用游戏逻辑分辨率
float screenWidth = GAME_WIDTH;
float screenHeight = GAME_HEIGHT;
// 整体居中x 坐标相对于屏幕中心)
setPosition(extra2d::Vec2(screenWidth / 2.0f, screenHeight));
// 显示 "Game Over" 文字y=120从顶部开始
auto gameOverFrame = ResLoader::getKeyFrame("text_game_over");
if (gameOverFrame) {
auto gameOver = extra2d::Sprite::create(gameOverFrame->getTexture(),
gameOverFrame->getRect());
gameOver->setAnchor(extra2d::Vec2(0.5f, 0.0f));
gameOver->setPosition(extra2d::Vec2(0.0f, 120.0f)); // x=0 表示相对于中心点
addChild(gameOver);
}
// 初始化得分面板
initPanel(score_, screenHeight);
// 初始化按钮
initButtons();
// 创建向上移动的动画(从屏幕底部移动到正常位置)
auto moveAction = extra2d::MoveBy::create(1.0f, extra2d::Vec2(0.0f, -screenHeight));
moveAction->setCompletionCallback([this]() {
animationDone_ = true;
if (restartBtn_)
restartBtn_->setEnabled(true);
if (menuBtn_)
menuBtn_->setEnabled(true);
if (shareBtn_)
shareBtn_->setEnabled(true);
});
runAction(moveAction);
}
void GameOverLayer::initPanel(int score, float screenHeight) {
// 显示得分板(在屏幕中间)
auto panelFrame = ResLoader::getKeyFrame("score_panel");
if (!panelFrame)
return;
auto panel =
extra2d::Sprite::create(panelFrame->getTexture(), panelFrame->getRect());
panel->setAnchor(extra2d::Vec2(0.5f, 0.5f));
panel->setPosition(
extra2d::Vec2(0.0f, screenHeight / 2.0f)); // x=0 表示相对于中心点
addChild(panel);
// 获取最高分(从存储中读取)
static int bestScore = 0;
if (score > bestScore) {
bestScore = score;
}
// 显示 "New" 标记(如果破了记录)
if (score >= bestScore && score > 0) {
auto newFrame = ResLoader::getKeyFrame("new");
if (newFrame) {
auto newSprite =
extra2d::Sprite::create(newFrame->getTexture(), newFrame->getRect());
newSprite->setAnchor(extra2d::Vec2(0.5f, 0.5f));
// 调整位置使其在面板内部,靠近 BEST 分数
newSprite->setPosition(
extra2d::Vec2(30.0f, 25.0f)); // 相对于面板的坐标,在 BEST 右侧
panel->addChild(newSprite);
}
}
// 显示奖牌
auto medalFrame = getMedal(score);
if (medalFrame) {
auto medal = extra2d::Sprite::create(medalFrame->getTexture(),
medalFrame->getRect());
medal->setAnchor(extra2d::Vec2(0.5f, 0.5f));
medal->setPosition(extra2d::Vec2(54.0f, 68.0f)); // 相对于面板的坐标
panel->addChild(medal);
}
// 显示本局得分
auto scoreNumber = extra2d::makePtr<Number>();
scoreNumber->setLittleNumber(score);
scoreNumber->setPosition(
extra2d::Vec2(80.0f, -15.0f)); // 相对于面板的坐标,右侧对齐
panel->addChild(scoreNumber);
// 显示最高分
auto bestNumber = extra2d::makePtr<Number>();
bestNumber->setLittleNumber(bestScore);
bestNumber->setPosition(
extra2d::Vec2(80.0f, 25.0f)); // 相对于面板的坐标,右侧对齐
panel->addChild(bestNumber);
}
void GameOverLayer::initButtons() {
auto restartFrame = ResLoader::getKeyFrame("button_restart");
if (restartFrame) {
restartBtn_ = extra2d::Button::create();
restartBtn_->setBackgroundImage(restartFrame->getTexture(),
restartFrame->getRect());
restartBtn_->setAnchor(extra2d::Vec2(0.5f, 0.5f));
restartBtn_->setPosition(extra2d::Vec2(0.0f, 360.0f));
restartBtn_->setEnabled(false);
restartBtn_->setOnClick([]() {
ResLoader::playMusic(MusicType::Click);
auto &app = extra2d::Application::instance();
app.scenes().replaceScene(extra2d::makePtr<GameScene>(),
extra2d::TransitionType::Fade, 0.5f);
});
addChild(restartBtn_);
}
auto menuFrame = ResLoader::getKeyFrame("button_menu");
if (menuFrame) {
menuBtn_ = extra2d::Button::create();
menuBtn_->setBackgroundImage(menuFrame->getTexture(), menuFrame->getRect());
menuBtn_->setAnchor(extra2d::Vec2(0.5f, 0.5f));
menuBtn_->setPosition(extra2d::Vec2(0.0f, 420.0f));
menuBtn_->setEnabled(false);
menuBtn_->setOnClick([]() {
ResLoader::playMusic(MusicType::Click);
auto &app = extra2d::Application::instance();
app.scenes().replaceScene(extra2d::makePtr<StartScene>(),
extra2d::TransitionType::Fade, 0.5f);
});
addChild(menuBtn_);
}
auto shareFrame = ResLoader::getKeyFrame("button_share");
if (shareFrame) {
shareBtn_ = extra2d::Button::create();
shareBtn_->setBackgroundImage(shareFrame->getTexture(),
shareFrame->getRect());
shareBtn_->setAnchor(extra2d::Vec2(0.5f, 0.5f));
shareBtn_->setPosition(extra2d::Vec2(0.0f, 460.0f));
shareBtn_->setEnabled(false);
shareBtn_->setOnClick([]() { ResLoader::playMusic(MusicType::Click); });
addChild(shareBtn_);
}
}
void GameOverLayer::onUpdate(float dt) {
Node::onUpdate(dt);
if (!animationDone_)
return;
auto &input = extra2d::Application::instance().input();
if (input.isButtonPressed(extra2d::GamepadButton::A)) {
ResLoader::playMusic(MusicType::Click);
auto &app = extra2d::Application::instance();
app.scenes().replaceScene(extra2d::makePtr<GameScene>(),
extra2d::TransitionType::Fade, 0.5f);
}
if (input.isButtonPressed(extra2d::GamepadButton::B)) {
ResLoader::playMusic(MusicType::Click);
auto &app = extra2d::Application::instance();
app.scenes().replaceScene(extra2d::makePtr<StartScene>(),
extra2d::TransitionType::Fade, 0.5f);
}
}
extra2d::Ptr<extra2d::SpriteFrame> GameOverLayer::getMedal(int score) {
if (score < 10) {
return nullptr; // 无奖牌
} else if (score < 20) {
return ResLoader::getKeyFrame("medals_0"); // 铜牌
} else if (score < 30) {
return ResLoader::getKeyFrame("medals_1"); // 银牌
} else if (score < 50) {
return ResLoader::getKeyFrame("medals_2"); // 金牌
} else {
return ResLoader::getKeyFrame("medals_3"); // 钻石奖牌
}
}
} // namespace flappybird

View File

@ -1,62 +0,0 @@
// ============================================================================
// GameOverLayer.h - 游戏结束层
// 描述: 显示游戏结束界面、得分和奖牌
// ============================================================================
#pragma once
#include <extra2d/extra2d.h>
namespace flappybird {
/**
* @brief
*
*/
class GameOverLayer : public extra2d::Node {
public:
/**
* @brief
* @param score
*/
GameOverLayer(int score);
/**
* @brief
*/
void onEnter() override;
/**
* @brief
* @param dt
*/
void onUpdate(float dt) override;
private:
/**
* @brief
* @param score
* @param screenHeight
*/
void initPanel(int score, float screenHeight);
/**
* @brief
*/
void initButtons();
/**
* @brief
* @param score
* @return
*/
extra2d::Ptr<extra2d::SpriteFrame> getMedal(int score);
int score_ = 0; // 本局得分
bool animationDone_ = false; // 动画是否完成
extra2d::Ptr<extra2d::Button> restartBtn_; // 重新开始按钮
extra2d::Ptr<extra2d::Button> menuBtn_; // 菜单按钮
extra2d::Ptr<extra2d::Button> shareBtn_; // 分享按钮
};
} // namespace flappybird

View File

@ -1,227 +0,0 @@
// ============================================================================
// GameScene.cpp - 游戏主场景实现
// ============================================================================
#include "GameScene.h"
#include "GameOverLayer.h"
#include "ResLoader.h"
#include "input.h"
namespace flappybird {
GameScene::GameScene() {
// 基类 BaseScene 已经处理了视口设置和背景颜色
}
void GameScene::onEnter() {
BaseScene::onEnter();
// 游戏坐标系:使用游戏逻辑分辨率
float screenWidth = GAME_WIDTH;
float screenHeight = GAME_HEIGHT;
// 添加背景(使用左上角锚点,与原游戏一致)
auto bgFrame = ResLoader::getKeyFrame("bg_day");
if (bgFrame) {
auto background =
extra2d::Sprite::create(bgFrame->getTexture(), bgFrame->getRect());
background->setAnchor(extra2d::Vec2(0.0f, 0.0f));
background->setPosition(extra2d::Vec2(0.0f, 0.0f));
addChild(background);
}
// 添加水管(初始时隐藏,游戏开始后才显示)
auto pipes = extra2d::makePtr<Pipes>();
pipes_ = pipes.get();
pipes->setVisible(false);
addChild(pipes);
// 添加小鸟(在屏幕中间偏左位置)
auto bird = extra2d::makePtr<Bird>();
bird->setPosition(
extra2d::Vec2(screenWidth / 2.0f - 50.0f, screenHeight / 2.0f));
bird_ = bird.get();
addChild(bird);
// 添加地面
auto ground = extra2d::makePtr<Ground>();
ground_ = ground.get();
addChild(ground);
// 添加得分(屏幕顶部中央)
auto scoreNumber = extra2d::makePtr<Number>();
scoreNumber->setPosition(extra2d::Vec2(screenWidth / 2.0f, 50.0f));
scoreNumber->setNumber(0);
scoreNumber_ = scoreNumber.get();
addChild(scoreNumber);
// 添加 ready 图片(屏幕中央偏上)
auto readyFrame = ResLoader::getKeyFrame("text_ready");
if (readyFrame) {
readySprite_ = extra2d::Sprite::create(readyFrame->getTexture(),
readyFrame->getRect());
readySprite_->setAnchor(extra2d::Vec2(0.5f, 0.5f));
readySprite_->setPosition(
extra2d::Vec2(screenWidth / 2.0f, screenHeight / 2.0f - 70.0f));
addChild(readySprite_);
}
// 添加教程图片(屏幕中央偏下)
auto tutorialFrame = ResLoader::getKeyFrame("tutorial");
if (tutorialFrame) {
tutorialSprite_ = extra2d::Sprite::create(tutorialFrame->getTexture(),
tutorialFrame->getRect());
tutorialSprite_->setAnchor(extra2d::Vec2(0.5f, 0.5f));
tutorialSprite_->setPosition(
extra2d::Vec2(screenWidth / 2.0f, screenHeight / 2.0f + 30.0f));
addChild(tutorialSprite_);
}
// 播放转场音效
ResLoader::playMusic(MusicType::Swoosh);
// 初始化状态
started_ = false;
score_ = 0;
}
void GameScene::onUpdate(float dt) {
if (!gameOver_) {
if (!bird_)
return;
auto &input = extra2d::Application::instance().input();
if (input.isButtonPressed(extra2d::GamepadButton::A) ||
input.isMousePressed(extra2d::MouseButton::Left)) {
if (!started_) {
started_ = true;
startGame();
}
bird_->jump();
}
if (started_) {
bird_->fall(dt);
if (pipes_) {
Pipe *firstPipe = pipes_->getPipe(0);
if (firstPipe && !firstPipe->scored) {
float birdX = bird_->getPosition().x;
float pipeX = firstPipe->getPosition().x;
if (pipeX <= birdX) {
score_++;
scoreNumber_->setNumber(score_);
firstPipe->scored = true;
ResLoader::playMusic(MusicType::Point);
}
}
}
if (bird_->isLiving() && checkCollision()) {
onHit();
}
if (bird_->isLiving() && GAME_HEIGHT - bird_->getPosition().y <= 123.0f) {
bird_->setPosition(
extra2d::Vec2(bird_->getPosition().x, GAME_HEIGHT - 123.0f));
bird_->setStatus(Bird::Status::Still);
onHit();
}
}
}
BaseScene::onUpdate(dt);
}
void GameScene::startGame() {
// 隐藏 ready 和 tutorial 图片
if (readySprite_) {
readySprite_->setVisible(false);
}
if (tutorialSprite_) {
tutorialSprite_->setVisible(false);
}
// 显示并开始移动水管
if (pipes_) {
pipes_->setVisible(true);
pipes_->start();
}
// 设置小鸟状态
if (bird_) {
bird_->setStatus(Bird::Status::StartToFly);
}
}
bool GameScene::checkCollision() {
if (!bird_ || !pipes_)
return false;
extra2d::Rect birdBox = bird_->getBoundingBox();
// 检查与每个水管的碰撞
for (int i = 0; i < 3; ++i) {
Pipe *pipe = pipes_->getPipe(i);
if (!pipe)
continue;
// 检查与上水管的碰撞
extra2d::Rect topBox = pipe->getTopPipeBox();
if (birdBox.intersects(topBox)) {
return true;
}
// 检查与下水管的碰撞
extra2d::Rect bottomBox = pipe->getBottomPipeBox();
if (birdBox.intersects(bottomBox)) {
return true;
}
}
return false;
}
void GameScene::onHit() {
if (!bird_->isLiving())
return;
// 小鸟死亡
bird_->die();
// 停止地面滚动
if (ground_) {
ground_->stop();
}
// 停止水管移动
if (pipes_) {
pipes_->stop();
}
// 停止小鸟动画
if (bird_) {
bird_->setStatus(Bird::Status::Still);
}
// 隐藏得分
if (scoreNumber_) {
scoreNumber_->setVisible(false);
}
gameOver();
}
void GameScene::gameOver() {
if (gameOver_)
return;
started_ = false;
gameOver_ = true;
auto gameOverLayer = extra2d::makePtr<GameOverLayer>(score_);
addChild(gameOverLayer);
}
} // namespace flappybird

View File

@ -1,72 +0,0 @@
// ============================================================================
// GameScene.h - 游戏主场景
// 描述: 游戏的核心场景,包含小鸟、水管、地面和得分系统
// ============================================================================
#pragma once
#include "BaseScene.h"
#include "Bird.h"
#include "Pipes.h"
#include "Ground.h"
#include "Number.h"
namespace flappybird {
/**
* @brief
*
*/
class GameScene : public BaseScene {
public:
/**
* @brief
*/
GameScene();
/**
* @brief
*/
void onEnter() override;
/**
* @brief
* @param dt
*/
void onUpdate(float dt) override;
private:
/**
* @brief
*/
void startGame();
/**
* @brief
*/
void onHit();
/**
* @brief
*/
void gameOver();
/**
* @brief
* @return
*/
bool checkCollision();
Bird* bird_ = nullptr; // 小鸟
Pipes* pipes_ = nullptr; // 水管管理器
Ground* ground_ = nullptr; // 地面
Number* scoreNumber_ = nullptr; // 得分显示
extra2d::Ptr<extra2d::Sprite> readySprite_; // "Get Ready" 提示
extra2d::Ptr<extra2d::Sprite> tutorialSprite_; // 操作教程提示
bool started_ = false; // 游戏是否已开始
bool gameOver_ = false; // 游戏是否已结束
int score_ = 0; // 当前得分
};
} // namespace flappybird

View File

@ -1,66 +0,0 @@
// ============================================================================
// Number.cpp - 数字显示类实现
// ============================================================================
#include "Number.h"
#include "ResLoader.h"
namespace flappybird {
Number::Number() : number_(0) {
}
void Number::setNumber(int number) {
number_ = number;
createNumberSprites(number, "number_big_");
}
void Number::setLittleNumber(int number) {
number_ = number;
createNumberSprites(number, "number_medium_");
}
void Number::createNumberSprites(int number, const std::string& prefix) {
// 清除之前的数字精灵
removeAllChildren();
// 获取数字 0 的高度作为参考
auto zeroFrame = ResLoader::getKeyFrame(prefix + "0");
float digitHeight = zeroFrame ? zeroFrame->getRect().size.height : 36.0f;
// 收集所有数字位
std::vector<int> digits;
if (number == 0) {
digits.push_back(0);
} else {
while (number > 0) {
digits.push_back(number % 10);
number /= 10;
}
}
// 计算总宽度
float totalWidth = 0.0f;
std::vector<float> digitWidths;
for (int digit : digits) {
auto frame = ResLoader::getKeyFrame(prefix + std::to_string(digit));
float width = frame ? frame->getRect().size.width : 24.0f;
digitWidths.push_back(width);
totalWidth += width;
}
// 创建数字精灵并居中排列
float currentX = -totalWidth / 2.0f;
for (size_t i = 0; i < digits.size(); ++i) {
auto frame = ResLoader::getKeyFrame(prefix + std::to_string(digits[i]));
if (frame) {
auto digitSprite = extra2d::Sprite::create(frame->getTexture(), frame->getRect());
digitSprite->setAnchor(extra2d::Vec2(0.0f, 0.0f));
digitSprite->setPosition(extra2d::Vec2(currentX, -digitHeight / 2.0f));
addChild(digitSprite);
}
currentX += digitWidths[i];
}
}
} // namespace flappybird

View File

@ -1,52 +0,0 @@
// ============================================================================
// Number.h - 数字显示类
// 描述: 将整数数字转换为精灵图片显示
// ============================================================================
#pragma once
#include <extra2d/extra2d.h>
namespace flappybird {
/**
* @brief
*
*/
class Number : public extra2d::Node {
public:
/**
* @brief
*/
Number();
/**
* @brief
* @param number
*/
void setNumber(int number);
/**
* @brief
* @param number
*/
void setLittleNumber(int number);
/**
* @brief
* @return
*/
int getNumber() const { return number_; }
private:
/**
* @brief
* @param number
* @param prefix "number_big_" "number_medium_"
*/
void createNumberSprites(int number, const std::string& prefix);
int number_ = 0; // 当前数字
};
} // namespace flappybird

View File

@ -1,99 +0,0 @@
// ============================================================================
// ResLoader.cpp - 资源加载器实现
// ============================================================================
#include "ResLoader.h"
#include <json/json.hpp>
namespace flappybird {
extra2d::Ptr<extra2d::Texture> ResLoader::atlasTexture_;
std::map<std::string, ResLoader::ImageInfo> ResLoader::imageMap_;
std::map<MusicType, extra2d::Ptr<extra2d::Sound>> ResLoader::soundMap_;
void ResLoader::init() {
auto &resources = extra2d::Application::instance().resources();
// 加载图集纹理
atlasTexture_ = resources.loadTexture("assets/images/atlas.png");
if (!atlasTexture_) {
E2D_LOG_ERROR("无法加载图集纹理 atlas.png");
return;
}
// 使用资源管理器加载 JSON 文件
std::string jsonContent = resources.loadJsonFile("assets/images/atlas.json");
if (jsonContent.empty()) {
E2D_LOG_ERROR("无法加载 atlas.json 文件");
return;
}
// 解析 JSON 图集数据
try {
nlohmann::json jsonData = nlohmann::json::parse(jsonContent);
for (const auto &sprite : jsonData["sprites"]) {
std::string name = sprite["name"];
float x = sprite["x"];
float y = sprite["y"];
float width = sprite["width"];
float height = sprite["height"];
ImageInfo info = {width, height, x, y};
imageMap_[name] = info;
}
E2D_LOG_INFO("成功加载 {} 个精灵帧", imageMap_.size());
} catch (const std::exception &e) {
E2D_LOG_ERROR("解析 atlas.json 失败: {}", e.what());
return;
}
// 加载音效
soundMap_[MusicType::Click] = resources.loadSound("assets/sound/click.wav");
soundMap_[MusicType::Hit] = resources.loadSound("assets/sound/hit.wav");
soundMap_[MusicType::Fly] = resources.loadSound("assets/sound/fly.wav");
soundMap_[MusicType::Point] = resources.loadSound("assets/sound/point.wav");
soundMap_[MusicType::Swoosh] = resources.loadSound("assets/sound/swoosh.wav");
E2D_LOG_INFO("资源加载完成");
}
extra2d::Ptr<extra2d::SpriteFrame>
ResLoader::getKeyFrame(const std::string &name) {
auto it = imageMap_.find(name);
if (it == imageMap_.end()) {
E2D_LOG_WARN("找不到精灵帧: %s", name.c_str());
return nullptr;
}
const ImageInfo &info = it->second;
E2D_LOG_INFO("加载精灵帧: name={}, w={}, h={}, x={}, y={}", name, info.width,
info.height, info.x, info.y);
// 检查纹理尺寸
if (atlasTexture_) {
E2D_LOG_INFO("图集纹理尺寸: {}x{}", atlasTexture_->getWidth(),
atlasTexture_->getHeight());
}
return extra2d::makePtr<extra2d::SpriteFrame>(
atlasTexture_, extra2d::Rect(info.x, info.y, info.width, info.height));
}
void ResLoader::playMusic(MusicType type) {
auto it = soundMap_.find(type);
if (it == soundMap_.end()) {
E2D_LOG_WARN("ResLoader::playMusic: sound type not found");
return;
}
if (!it->second) {
E2D_LOG_WARN("ResLoader::playMusic: sound pointer is null");
return;
}
if (!it->second->play()) {
E2D_LOG_WARN("ResLoader::playMusic: failed to play sound");
}
}
} // namespace flappybird

View File

@ -1,63 +0,0 @@
// ============================================================================
// ResLoader.h - 资源加载器
// 描述: 管理游戏资源的加载和访问
// ============================================================================
#pragma once
#include <extra2d/extra2d.h>
#include <map>
#include <string>
namespace flappybird {
/**
* @brief
*/
enum class MusicType {
Click, // 按键声音
Hit, // 小鸟死亡声音
Fly, // 小鸟飞翔声音
Point, // 得分声音
Swoosh // 转场声音
};
/**
* @brief
*
*/
class ResLoader {
public:
/**
* @brief
*/
static void init();
/**
* @brief
* @param name
* @return
*/
static extra2d::Ptr<extra2d::SpriteFrame> getKeyFrame(const std::string& name);
/**
* @brief
* @param type
*/
static void playMusic(MusicType type);
private:
/**
* @brief
* atlas.txt : width height x y
*/
struct ImageInfo {
float width, height, x, y;
};
static extra2d::Ptr<extra2d::Texture> atlasTexture_; // 图集纹理
static std::map<std::string, ImageInfo> imageMap_; // 图片信息映射
static std::map<MusicType, extra2d::Ptr<extra2d::Sound>> soundMap_; // 音效映射
};
} // namespace flappybird

View File

@ -1,49 +0,0 @@
// ============================================================================
// SplashScene.cpp - 启动场景实现
// ============================================================================
#include "SplashScene.h"
#include "ResLoader.h"
#include "StartScene.h"
#include <extra2d/utils/logger.h>
namespace flappybird {
SplashScene::SplashScene() {
// 基类 BaseScene 已经处理了视口设置和背景颜色
}
void SplashScene::onEnter() {
BaseScene::onEnter();
// 尝试加载 splash 图片
auto splashFrame = ResLoader::getKeyFrame("splash");
if (splashFrame) {
auto splash = extra2d::Sprite::create(splashFrame->getTexture(),
splashFrame->getRect());
splash->setAnchor(0.5f, 0.5f);
// splash 图片是全屏的(288x512),将其中心放在游戏区域中心
splash->setPosition(GAME_WIDTH / 2.0f, GAME_HEIGHT / 2.0f);
addChild(splash);
}
// 播放转场音效
ResLoader::playMusic(MusicType::Swoosh);
}
void SplashScene::onUpdate(float dt) {
BaseScene::onUpdate(dt);
// 计时
timer_ += dt;
if (timer_ >= delay_) {
gotoStartScene();
}
}
void SplashScene::gotoStartScene() {
auto &app = extra2d::Application::instance();
app.scenes().replaceScene(extra2d::makePtr<StartScene>(),
extra2d::TransitionType::Fade, 2.0f);
}
} // namespace flappybird

View File

@ -1,44 +0,0 @@
// ============================================================================
// SplashScene.h - 启动场景
// 描述: 显示游戏 Logo2秒后自动跳转到开始场景
// ============================================================================
#pragma once
#include "BaseScene.h"
namespace flappybird {
/**
* @brief
* Logo
*/
class SplashScene : public BaseScene {
public:
/**
* @brief
*/
SplashScene();
/**
* @brief
*/
void onEnter() override;
/**
* @brief
* @param dt
*/
void onUpdate(float dt) override;
private:
/**
* @brief
*/
void gotoStartScene();
float timer_ = 0.0f; // 计时器
const float delay_ = 2.0f; // 延迟时间(秒)
};
} // namespace flappybird

View File

@ -1,145 +0,0 @@
// ============================================================================
// StartScene.cpp - 开始菜单场景实现
// ============================================================================
#include "StartScene.h"
#include "Bird.h"
#include "GameScene.h"
#include "Ground.h"
#include "ResLoader.h"
#include "extra2d/event/input_codes.h"
namespace flappybird {
StartScene::StartScene() {
// 基类 BaseScene 已经处理了视口设置和背景颜色
}
void StartScene::onEnter() {
BaseScene::onEnter();
// 使用游戏逻辑分辨率
float screenWidth = GAME_WIDTH;
float screenHeight = GAME_HEIGHT;
// 添加背景(使用左上角锚点)
auto bgFrame = ResLoader::getKeyFrame("bg_day");
if (bgFrame) {
auto background =
extra2d::Sprite::create(bgFrame->getTexture(), bgFrame->getRect());
background->setAnchor(0.0f, 0.0f);
background->setPosition(0.0f, 0.0f);
addChild(background);
E2D_LOG_INFO("背景已添加: size={} x {}", bgFrame->getRect().size.width,
bgFrame->getRect().size.height);
} else {
E2D_LOG_ERROR("无法加载背景图片");
}
// 添加地面
auto ground = extra2d::makePtr<Ground>();
addChild(ground);
// 添加标题图片(在上方)
auto titleFrame = ResLoader::getKeyFrame("title");
if (titleFrame) {
auto title = extra2d::Sprite::create(titleFrame->getTexture(),
titleFrame->getRect());
title->setAnchor(0.5f, 0.5f);
// 标题在屏幕上方
title->setPosition(screenWidth / 2.0f, 150.0f);
addChild(title);
E2D_LOG_INFO("标题已添加: size={} x {}", titleFrame->getRect().size.width,
titleFrame->getRect().size.height);
} else {
E2D_LOG_ERROR("无法加载标题图片");
}
// 添加小鸟(在标题下方)
auto bird = extra2d::makePtr<Bird>();
bird->setAnchor(0.5f, 0.5f);
bird->setPosition(screenWidth / 2.0f, screenHeight / 2.0f);
bird->setStatus(Bird::Status::Idle);
addChild(bird);
// 添加开始按钮 - 在小鸟下方
auto playFrame = ResLoader::getKeyFrame("button_play");
if (playFrame) {
float btnWidth = playFrame->getRect().size.width;
float btnHeight = playFrame->getRect().size.height;
playBtn_ = extra2d::Button::create();
playBtn_->setBackgroundImage(playFrame->getTexture(), playFrame->getRect());
// 使用世界坐标,中心锚点
playBtn_->setAnchor(0.5f, 0.5f);
// PLAY 按钮在小鸟下方
playBtn_->setPosition(screenWidth / 2.0f,
screenHeight - playBtn_->getSize().height - 100.0f);
playBtn_->setOnClick([this]() {
ResLoader::playMusic(MusicType::Click);
startGame();
});
addChild(playBtn_);
}
// 添加分享按钮 - 在 PLAY 按钮下方,靠近地面
auto shareFrame = ResLoader::getKeyFrame("button_share");
if (shareFrame) {
float btnWidth = shareFrame->getRect().size.width;
float btnHeight = shareFrame->getRect().size.height;
shareBtn_ = extra2d::Button::create();
shareBtn_->setBackgroundImage(shareFrame->getTexture(),
shareFrame->getRect());
// 使用世界坐标,中心锚点
shareBtn_->setAnchor(0.5f, 0.5f);
// SHARE 按钮在 PLAY 按钮下方,靠近地面
shareBtn_->setPosition(screenWidth / 2.0f,
screenHeight - shareBtn_->getSize().height - 80.0f);
shareBtn_->setOnClick([this]() {
ResLoader::playMusic(MusicType::Click);
// 分享功能暂不实现
});
addChild(shareBtn_);
}
// 添加 copyright 图片(在底部)
auto copyrightFrame = ResLoader::getKeyFrame("brand_copyright");
if (copyrightFrame) {
auto copyright = extra2d::Sprite::create(copyrightFrame->getTexture(),
copyrightFrame->getRect());
copyright->setAnchor(0.5f, 0.5f);
// Copyright 在屏幕底部
copyright->setPosition(screenWidth / 2.0f, screenHeight - 20.0f);
addChild(copyright);
}
// 播放转场音效
ResLoader::playMusic(MusicType::Swoosh);
}
void StartScene::onUpdate(float dt) {
BaseScene::onUpdate(dt);
// 检测 A 键或空格开始游戏
auto &input = extra2d::Application::instance().input();
if (input.isButtonPressed(extra2d::GamepadButton::A)) {
ResLoader::playMusic(MusicType::Click);
startGame();
}
// 检测 BACK 键退出游戏
if (input.isButtonPressed(extra2d::GamepadButton::Start)) {
ResLoader::playMusic(MusicType::Click);
auto &app = extra2d::Application::instance();
app.quit();
}
}
void StartScene::startGame() {
auto &app = extra2d::Application::instance();
app.scenes().replaceScene(extra2d::makePtr<GameScene>(),
extra2d::TransitionType::Fade, 0.5f);
}
} // namespace flappybird

View File

@ -1,53 +0,0 @@
// ============================================================================
// StartScene.h - 开始菜单场景
// 描述: 显示游戏标题、开始按钮和版权信息
// ============================================================================
#pragma once
#include "BaseScene.h"
namespace flappybird {
/**
* @brief
*
*/
class StartScene : public BaseScene {
public:
/**
* @brief
*/
StartScene();
/**
* @brief
*/
void onEnter() override;
/**
* @brief
* @param dt
*/
void onUpdate(float dt) override;
private:
/**
* @brief
*/
void createMenuButtons();
/**
* @brief
*/
void startGame();
extra2d::Ptr<extra2d::Button> playBtn_; // 开始按钮
extra2d::Ptr<extra2d::Button> shareBtn_; // 分享按钮
extra2d::Ptr<extra2d::Sprite> title_; // 标题精灵
float titleFinalY_ = 0.0f; // 标题最终Y位置
float titleAnimTime_ = 0.0f; // 标题动画时间
static constexpr float TITLE_ANIM_DURATION = 0.5f; // 标题动画持续时间
};
} // namespace flappybird

View File

@ -1,188 +0,0 @@
// ============================================================================
// Bird.cpp - 小鸟类实现
// ============================================================================
#include "Bird.h"
#include "ResLoader.h"
namespace flappybird {
Bird::Bird() {
// 注意:不要在构造函数中调用 initAnimations()
// 因为此时 weak_from_this() 还不能使用
setStatus(Status::Idle);
}
void Bird::onEnter() {
Node::onEnter();
// 在 onEnter 中初始化动画,此时 weak_from_this() 可用
if (!animSprite_) {
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
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));
} 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() ? "" : "");
} else {
E2D_LOG_ERROR("小鸟动画创建失败: 没有找到任何动画帧");
}
}
void Bird::onUpdate(float dt) {
extra2d::Node::onUpdate(dt);
// 处理闲置动画(上下浮动)
if (status_ == Status::Idle) {
idleTimer_ += dt;
idleOffset_ = std::sin(idleTimer_ * 5.0f) * 4.0f;
}
}
void Bird::onRender(extra2d::RenderBackend& renderer) {
// 动画精灵会自动渲染,这里只需要处理旋转和偏移
if (animSprite_) {
animSprite_->setRotation(rotation_);
// 应用闲置偏移
if (status_ == Status::Idle) {
animSprite_->setPosition(extra2d::Vec2(0.0f, idleOffset_));
} else {
animSprite_->setPosition(extra2d::Vec2(0.0f, 0.0f));
}
}
// 调用父类的 onRender 来渲染子节点
Node::onRender(renderer);
}
void Bird::fall(float dt) {
if (!living_) return;
// 更新垂直位置
extra2d::Vec2 pos = getPosition();
pos.y += speed_ * dt;
setPosition(pos);
// 应用重力
speed_ += gravity * dt;
// 限制顶部边界
if (pos.y < 0) {
pos.y = 0;
setPosition(pos);
speed_ = 0;
}
// 根据速度计算旋转角度
// 上升时抬头(-15度),下降时低头(最大90度)
if (speed_ < 0) {
rotation_ = -15.0f;
} else {
rotation_ = std::min(90.0f, speed_ * 0.15f);
}
}
void Bird::jump() {
if (!living_) return;
// 给小鸟向上的速度
speed_ = -jumpSpeed;
// 设置状态为飞行
setStatus(Status::Fly);
// 播放音效
ResLoader::playMusic(MusicType::Fly);
}
void Bird::die() {
living_ = false;
// 播放死亡音效
ResLoader::playMusic(MusicType::Hit);
}
void Bird::setStatus(Status status) {
status_ = status;
switch (status) {
case Status::Still:
// 停止所有动画
if (animSprite_) {
animSprite_->pause();
}
break;
case Status::Idle:
// 开始闲置动画
if (animSprite_) {
animSprite_->setPlaybackSpeed(1.0f); // 正常速度
animSprite_->play();
}
idleTimer_ = 0.0f;
break;
case Status::StartToFly:
// 停止闲置动画,加速翅膀扇动
idleOffset_ = 0.0f;
if (animSprite_) {
animSprite_->setPlaybackSpeed(2.0f); // 2倍速度 = 0.05秒每帧
}
break;
case Status::Fly:
// 飞行状态
break;
default:
break;
}
}
extra2d::Rect Bird::getBoundingBox() const {
extra2d::Vec2 pos = getPosition();
// 小鸟碰撞框大小约为 24x24
float halfSize = 12.0f;
return extra2d::Rect(
pos.x - halfSize,
pos.y - halfSize,
halfSize * 2.0f,
halfSize * 2.0f
);
}
} // namespace flappybird

View File

@ -1,116 +0,0 @@
// ============================================================================
// Bird.h - 小鸟类
// 描述: 玩家控制的小鸟角色,包含飞行动画和物理效果
// ============================================================================
#pragma once
#include <extra2d/extra2d.h>
namespace flappybird {
/**
* @brief
*
*/
class Bird : public extra2d::Node {
public:
/**
* @brief
*/
enum class Status {
Still, // 静止不动
Idle, // 上下浮动(菜单展示)
StartToFly, // 开始飞行
Fly // 飞行中
};
/**
* @brief
*/
Bird();
/**
* @brief
*/
~Bird();
/**
* @brief
* @param dt
*/
void onUpdate(float dt) override;
/**
* @brief
* @param renderer
*/
void onRender(extra2d::RenderBackend& renderer) override;
/**
* @brief
*/
void onEnter() override;
/**
* @brief
* @param dt
*/
void fall(float dt);
/**
* @brief
*/
void jump();
/**
* @brief
*/
void die();
/**
* @brief
* @param status
*/
void setStatus(Status status);
/**
* @brief
* @return
*/
Status getStatus() const { return status_; }
/**
* @brief
* @return
*/
bool isLiving() const { return living_; }
/**
* @brief
* @return
*/
extra2d::Rect getBoundingBox() const override;
private:
/**
* @brief
*/
void initAnimations();
bool living_ = true; // 是否存活
float speed_ = 0.0f; // 垂直速度
float rotation_ = 0.0f; // 旋转角度
Status status_ = Status::Idle; // 当前状态
// 动画相关
extra2d::Ptr<extra2d::AnimatedSprite> animSprite_; // 动画精灵
float idleTimer_ = 0.0f; // 闲置动画计时器
float idleOffset_ = 0.0f; // 闲置偏移量
// 物理常量
static constexpr float gravity = 1440.0f; // 重力加速度
static constexpr float jumpSpeed = 432.0f; // 跳跃初速度
};
} // namespace flappybird

View File

@ -1,77 +0,0 @@
// ============================================================================
// Ground.cpp - 地面类实现
// ============================================================================
#include "Ground.h"
#include "ResLoader.h"
#include "BaseScene.h"
namespace flappybird {
Ground::Ground() {
moving_ = true;
// 使用游戏逻辑高度,而不是窗口高度
float screenHeight = GAME_HEIGHT;
// 获取地面纹理帧
auto landFrame = ResLoader::getKeyFrame("land");
if (!landFrame) return;
// 获取地面纹理和矩形
auto texture = landFrame->getTexture();
auto rect = landFrame->getRect();
float groundWidth = rect.size.width;
float groundHeight = rect.size.height;
// 创建第一块地面
ground1_ = extra2d::Sprite::create(texture, rect);
ground1_->setAnchor(extra2d::Vec2(0.0f, 1.0f)); // 锚点设在左下角
ground1_->setPosition(extra2d::Vec2(0.0f, screenHeight));
addChild(ground1_);
// 创建第二块地面,紧挨在第一块右边
ground2_ = extra2d::Sprite::create(texture, rect);
ground2_->setAnchor(extra2d::Vec2(0.0f, 1.0f));
ground2_->setPosition(extra2d::Vec2(groundWidth - 1.0f, screenHeight));
addChild(ground2_);
}
void Ground::onUpdate(float dt) {
extra2d::Node::onUpdate(dt);
if (!moving_) return;
if (!ground1_ || !ground2_) return;
// 获取地面宽度(从纹理矩形获取)
float groundWidth = ground1_->getTextureRect().size.width;
// 移动两块地面
extra2d::Vec2 pos1 = ground1_->getPosition();
extra2d::Vec2 pos2 = ground2_->getPosition();
pos1.x -= speed * dt;
pos2.x -= speed * dt;
// 当地面完全移出屏幕左侧时,重置到右侧
if (pos1.x <= -groundWidth) {
pos1.x = pos2.x + groundWidth - 1.0f;
}
if (pos2.x <= -groundWidth) {
pos2.x = pos1.x + groundWidth - 1.0f;
}
ground1_->setPosition(pos1);
ground2_->setPosition(pos2);
}
void Ground::stop() {
moving_ = false;
}
float Ground::getHeight() const {
auto landFrame = ResLoader::getKeyFrame("land");
return landFrame ? landFrame->getRect().size.height : 112.0f;
}
} // namespace flappybird

View File

@ -1,48 +0,0 @@
// ============================================================================
// Ground.h - 地面类
// 描述: 游戏底部不断向左滚动的地面
// ============================================================================
#pragma once
#include <extra2d/extra2d.h>
namespace flappybird {
/**
* @brief
*
*/
class Ground : public extra2d::Node {
public:
/**
* @brief
*/
Ground();
/**
* @brief
* @param dt
*/
void onUpdate(float dt) override;
/**
* @brief
*/
void stop();
/**
* @brief
* @return
*/
float getHeight() const;
private:
extra2d::Ptr<extra2d::Sprite> ground1_; // 第一块地面
extra2d::Ptr<extra2d::Sprite> ground2_; // 第二块地面
static constexpr float speed = 120.0f; // 滚动速度(像素/秒)
bool moving_ = true; // 是否正在滚动
};
} // namespace flappybird

View File

@ -1,53 +0,0 @@
// ============================================================================
// FlappyBird - Extra2D 示例程序
// 作者: Extra2D Team
// 描述: 经典的 Flappy Bird 游戏实现
// ============================================================================
#include "ResLoader.h"
#include "SplashScene.h"
#include <extra2d/extra2d.h>
using namespace extra2d;
/**
* @brief
*/
int main(int argc, char **argv) {
// 初始化日志系统
Logger::init();
Logger::setLevel(LogLevel::Debug);
E2D_LOG_INFO("========================");
E2D_LOG_INFO("Extra2D FlappyBird");
E2D_LOG_INFO("========================");
// 获取应用实例
auto &app = Application::instance();
// 配置应用
AppConfig config;
config.title = "Extra2D - FlappyBird";
config.width = 1280; // 窗口宽度 (720P 分辨率)
config.height = 720; // 窗口高度 (720P 分辨率)
config.vsync = true;
config.fpsLimit = 60;
// 初始化应用
if (!app.init(config)) {
E2D_LOG_ERROR("应用初始化失败!");
return -1;
}
// 初始化资源加载器
flappybird::ResLoader::init();
// 进入启动场景
app.enterScene(makePtr<flappybird::SplashScene>());
E2D_LOG_INFO("开始主循环...");
app.run();
E2D_LOG_INFO("应用结束");
return 0;
}

View File

@ -1,113 +0,0 @@
// ============================================================================
// Pipe.cpp - 水管类实现
// ============================================================================
#include "Pipe.h"
#include "ResLoader.h"
#include "BaseScene.h"
namespace flappybird {
Pipe::Pipe() {
scored = false;
// 注意:不要在构造函数中创建子节点
// 因为此时 weak_from_this() 还不能使用
}
void Pipe::onEnter() {
Node::onEnter();
// 在 onEnter 中创建子节点,此时 weak_from_this() 可用
if (!topPipe_ && !bottomPipe_) {
// 使用游戏逻辑高度
float screenHeight = GAME_HEIGHT;
// 获取地面高度
auto landFrame = ResLoader::getKeyFrame("land");
float landHeight = landFrame ? landFrame->getRect().size.height : 112.0f;
// 随机生成水管高度
// 范围:与屏幕顶部最小距离不小于 100 像素
// 与屏幕底部最小距离不小于地面上方 100 像素
float minHeight = 100.0f;
float maxHeight = screenHeight - landHeight - 100.0f - gapHeight_;
float height = static_cast<float>(extra2d::randomInt(static_cast<int>(minHeight), static_cast<int>(maxHeight)));
// 创建上水管
auto topFrame = ResLoader::getKeyFrame("pipe_above");
if (topFrame) {
topPipe_ = extra2d::Sprite::create(topFrame->getTexture(), topFrame->getRect());
topPipe_->setAnchor(extra2d::Vec2(0.5f, 1.0f)); // 锚点设在底部中心
topPipe_->setPosition(extra2d::Vec2(0.0f, height - gapHeight_ / 2.0f));
addChild(topPipe_);
}
// 创建下水管
auto bottomFrame = ResLoader::getKeyFrame("pipe_below");
if (bottomFrame) {
bottomPipe_ = extra2d::Sprite::create(bottomFrame->getTexture(), bottomFrame->getRect());
bottomPipe_->setAnchor(extra2d::Vec2(0.5f, 0.0f)); // 锚点设在顶部中心
bottomPipe_->setPosition(extra2d::Vec2(0.0f, height + gapHeight_ / 2.0f));
addChild(bottomPipe_);
}
}
}
Pipe::~Pipe() = default;
extra2d::Rect Pipe::getBoundingBox() const {
// 返回整个水管的边界框(包含上下两根)
extra2d::Vec2 pos = getPosition();
// 水管宽度约为 52
float pipeWidth = 52.0f;
float halfWidth = pipeWidth / 2.0f;
// 使用游戏逻辑高度
float screenHeight = GAME_HEIGHT;
return extra2d::Rect(
pos.x - halfWidth,
0.0f,
pipeWidth,
screenHeight
);
}
extra2d::Rect Pipe::getTopPipeBox() const {
if (!topPipe_) return extra2d::Rect();
extra2d::Vec2 pos = getPosition();
extra2d::Vec2 topPos = topPipe_->getPosition();
// 上水管尺寸
float pipeWidth = 52.0f;
float pipeHeight = 320.0f;
return extra2d::Rect(
pos.x - pipeWidth / 2.0f,
pos.y + topPos.y - pipeHeight,
pipeWidth,
pipeHeight
);
}
extra2d::Rect Pipe::getBottomPipeBox() const {
if (!bottomPipe_) return extra2d::Rect();
extra2d::Vec2 pos = getPosition();
extra2d::Vec2 bottomPos = bottomPipe_->getPosition();
// 下水管尺寸
float pipeWidth = 52.0f;
float pipeHeight = 320.0f;
return extra2d::Rect(
pos.x - pipeWidth / 2.0f,
pos.y + bottomPos.y,
pipeWidth,
pipeHeight
);
}
} // namespace flappybird

View File

@ -1,59 +0,0 @@
// ============================================================================
// Pipe.h - 水管类
// 描述: 游戏中的障碍物,由上下两根水管组成
// ============================================================================
#pragma once
#include <extra2d/extra2d.h>
namespace flappybird {
/**
* @brief
*
*/
class Pipe : public extra2d::Node {
public:
/**
* @brief
*/
Pipe();
/**
* @brief
*/
~Pipe();
/**
* @brief
*/
void onEnter() override;
/**
* @brief
* @return
*/
extra2d::Rect getBoundingBox() const override;
/**
* @brief
* @return
*/
extra2d::Rect getTopPipeBox() const;
/**
* @brief
* @return
*/
extra2d::Rect getBottomPipeBox() const;
bool scored = false; // 是否已计分
private:
extra2d::Ptr<extra2d::Sprite> topPipe_; // 上水管
extra2d::Ptr<extra2d::Sprite> bottomPipe_; // 下水管
float gapHeight_ = 120.0f; // 间隙高度
};
} // namespace flappybird

View File

@ -1,106 +0,0 @@
// ============================================================================
// Pipes.cpp - 水管管理器实现
// ============================================================================
#include "Pipes.h"
#include "BaseScene.h"
namespace flappybird {
Pipes::Pipes() {
pipeCount_ = 0;
moving_ = false;
// 初始化水管数组
for (int i = 0; i < maxPipes; ++i) {
pipes_[i] = nullptr;
}
// 注意:不要在构造函数中添加水管
// 因为此时 weak_from_this() 还不能使用
}
void Pipes::onEnter() {
Node::onEnter();
// 在 onEnter 中初始化水管,此时 weak_from_this() 可用
if (pipeCount_ == 0) {
addPipe();
addPipe();
addPipe();
}
}
Pipes::~Pipes() = default;
void Pipes::onUpdate(float dt) {
extra2d::Node::onUpdate(dt);
if (!moving_) return;
// 移动所有水管
for (int i = 0; i < pipeCount_; ++i) {
if (pipes_[i]) {
extra2d::Vec2 pos = pipes_[i]->getPosition();
pos.x -= pipeSpeed * dt;
pipes_[i]->setPosition(pos);
}
}
// 检查最前面的水管是否移出屏幕
if (pipes_[0] && pipes_[0]->getPosition().x <= -30.0f) {
// 移除第一个水管(通过名称查找并移除)
// 由于 removeChild 需要 Ptr<Node>,我们使用 removeChildByName 或直接操作
// 这里我们直接移除第一个子节点(假设它是水管)
auto children = getChildren();
if (!children.empty()) {
removeChild(children[0]);
}
// 将后面的水管前移
for (int i = 0; i < pipeCount_ - 1; ++i) {
pipes_[i] = pipes_[i + 1];
}
pipes_[pipeCount_ - 1] = nullptr;
pipeCount_--;
// 添加新水管
addPipe();
}
}
void Pipes::addPipe() {
if (pipeCount_ >= maxPipes) return;
// 创建新水管
auto pipe = extra2d::makePtr<Pipe>();
// 设置水管位置
if (pipeCount_ == 0) {
// 第一个水管在屏幕外 130 像素处
pipe->setPosition(extra2d::Vec2(
GAME_WIDTH + 130.0f,
0.0f
));
} else {
// 其他水管在前一个水管后方
float prevX = pipes_[pipeCount_ - 1]->getPosition().x;
pipe->setPosition(extra2d::Vec2(prevX + pipeSpacing, 0.0f));
}
// 保存水管指针
pipes_[pipeCount_] = pipe.get();
pipeCount_++;
// 添加到场景
addChild(pipe);
}
void Pipes::start() {
moving_ = true;
}
void Pipes::stop() {
moving_ = false;
}
} // namespace flappybird

View File

@ -1,71 +0,0 @@
// ============================================================================
// Pipes.h - 水管管理器
// 描述: 管理多个水管的生成、移动和回收
// ============================================================================
#pragma once
#include <extra2d/extra2d.h>
#include "Pipe.h"
namespace flappybird {
/**
* @brief
*
*/
class Pipes : public extra2d::Node {
public:
/**
* @brief
*/
Pipes();
/**
* @brief
*/
~Pipes();
/**
* @brief
* @param dt
*/
void onUpdate(float dt) override;
/**
* @brief
*/
void onEnter() override;
/**
* @brief
*/
void start();
/**
* @brief
*/
void stop();
/**
* @brief
* @return
*/
Pipe* getPipe(int index) { return (index >= 0 && index < 3) ? pipes_[index] : nullptr; }
private:
/**
* @brief
*/
void addPipe();
static constexpr int maxPipes = 3; // 最大水管数量
static constexpr float pipeSpeed = 120.0f; // 水管移动速度(像素/秒)
static constexpr float pipeSpacing = 200.0f; // 水管间距
Pipe* pipes_[maxPipes]; // 水管数组
int pipeCount_ = 0; // 当前水管数量
bool moving_ = false; // 是否正在移动
};
} // namespace flappybird

View File

@ -1,78 +0,0 @@
{
"sprites": [
{"name": "splash", "width": 288, "height": 512, "x": 292, "y": 515},
{"name": "bg_day", "width": 288, "height": 512, "x": 0, "y": 0},
{"name": "bg_night", "width": 288, "height": 512, "x": 292, "y": 0},
{"name": "bird0_0", "width": 34, "height": 24, "x": 5, "y": 982},
{"name": "bird0_1", "width": 34, "height": 24, "x": 61, "y": 982},
{"name": "bird0_2", "width": 34, "height": 24, "x": 117, "y": 982},
{"name": "bird1_0", "width": 34, "height": 24, "x": 173, "y": 982},
{"name": "bird1_1", "width": 34, "height": 24, "x": 229, "y": 658},
{"name": "bird1_2", "width": 34, "height": 24, "x": 229, "y": 710},
{"name": "bird2_0", "width": 34, "height": 24, "x": 229, "y": 762},
{"name": "bird2_1", "width": 34, "height": 24, "x": 229, "y": 814},
{"name": "bird2_2", "width": 34, "height": 24, "x": 229, "y": 866},
{"name": "black", "width": 32, "height": 32, "x": 584, "y": 412},
{"name": "blink_00", "width": 10, "height": 10, "x": 276, "y": 682},
{"name": "blink_01", "width": 10, "height": 10, "x": 276, "y": 734},
{"name": "blink_02", "width": 10, "height": 10, "x": 276, "y": 786},
{"name": "brand_copyright", "width": 126, "height": 14, "x": 884, "y": 182},
{"name": "button_ok", "width": 80, "height": 28, "x": 924, "y": 84},
{"name": "button_pause", "width": 26, "height": 28, "x": 242, "y": 612},
{"name": "button_rate", "width": 74, "height": 48, "x": 924, "y": 0},
{"name": "button_resume", "width": 26, "height": 28, "x": 668, "y": 284},
{"name": "button_score", "width": 116, "height": 70, "x": 822, "y": 234},
{"name": "button_restart", "width": 116, "height": 70, "x": 702, "y": 316},
{"name": "button_share_big", "width": 116, "height": 70, "x": 822, "y": 316},
{"name": "button_play", "width": 116, "height": 70, "x": 702, "y": 234},
{"name": "button_menu", "width": 80, "height": 28, "x": 924, "y": 52},
{"name": "button_share", "width": 80, "height": 28, "x": 584, "y": 284},
{"name": "land", "width": 336, "height": 112, "x": 584, "y": 0},
{"name": "medals_0", "width": 44, "height": 44, "x": 224, "y": 954},
{"name": "medals_1", "width": 44, "height": 44, "x": 224, "y": 906},
{"name": "medals_2", "width": 44, "height": 44, "x": 242, "y": 564},
{"name": "medals_3", "width": 44, "height": 44, "x": 242, "y": 516},
{"name": "new", "width": 32, "height": 14, "x": 224, "y": 1002},
{"name": "number_big_0", "width": 24, "height": 36, "x": 992, "y": 120},
{"name": "number_big_1", "width": 16, "height": 36, "x": 272, "y": 910},
{"name": "number_big_2", "width": 24, "height": 36, "x": 584, "y": 320},
{"name": "number_big_3", "width": 24, "height": 36, "x": 612, "y": 320},
{"name": "number_big_4", "width": 24, "height": 36, "x": 640, "y": 320},
{"name": "number_big_5", "width": 24, "height": 36, "x": 668, "y": 320},
{"name": "number_big_6", "width": 24, "height": 36, "x": 584, "y": 368},
{"name": "number_big_7", "width": 24, "height": 36, "x": 612, "y": 368},
{"name": "number_big_8", "width": 24, "height": 36, "x": 640, "y": 368},
{"name": "number_big_9", "width": 24, "height": 36, "x": 668, "y": 368},
{"name": "number_medium_0", "width": 16, "height": 20, "x": 272, "y": 612},
{"name": "number_medium_1", "width": 16, "height": 20, "x": 272, "y": 954},
{"name": "number_medium_2", "width": 16, "height": 20, "x": 272, "y": 978},
{"name": "number_medium_3", "width": 16, "height": 20, "x": 260, "y": 1002},
{"name": "number_medium_4", "width": 16, "height": 20, "x": 1002, "y": 0},
{"name": "number_medium_5", "width": 16, "height": 20, "x": 1002, "y": 24},
{"name": "number_medium_6", "width": 16, "height": 20, "x": 1008, "y": 52},
{"name": "number_medium_7", "width": 16, "height": 20, "x": 1008, "y": 84},
{"name": "number_medium_8", "width": 16, "height": 20, "x": 584, "y": 484},
{"name": "number_medium_9", "width": 16, "height": 20, "x": 620, "y": 412},
{"name": "number_small_0", "width": 12, "height": 14, "x": 276, "y": 646},
{"name": "number_small_1", "width": 12, "height": 14, "x": 276, "y": 664},
{"name": "number_small_2", "width": 12, "height": 14, "x": 276, "y": 698},
{"name": "number_small_3", "width": 12, "height": 14, "x": 276, "y": 716},
{"name": "number_small_4", "width": 12, "height": 14, "x": 276, "y": 750},
{"name": "number_small_5", "width": 12, "height": 14, "x": 276, "y": 768},
{"name": "number_small_6", "width": 12, "height": 14, "x": 276, "y": 802},
{"name": "number_small_7", "width": 12, "height": 14, "x": 276, "y": 820},
{"name": "number_small_8", "width": 12, "height": 14, "x": 276, "y": 854},
{"name": "number_small_9", "width": 12, "height": 14, "x": 276, "y": 872},
{"name": "number_context_10", "width": 12, "height": 14, "x": 992, "y": 164},
{"name": "pipe_above_2", "width": 52, "height": 320, "x": 0, "y": 646},
{"name": "pipe_below_2", "width": 52, "height": 320, "x": 56, "y": 646},
{"name": "pipe_above", "width": 52, "height": 320, "x": 112, "y": 646},
{"name": "pipe_below", "width": 52, "height": 320, "x": 168, "y": 646},
{"name": "score_panel", "width": 238, "height": 126, "x": 0, "y": 516},
{"name": "text_game_over", "width": 204, "height": 54, "x": 784, "y": 116},
{"name": "text_ready", "width": 196, "height": 62, "x": 584, "y": 116},
{"name": "title", "width": 178, "height": 48, "x": 702, "y": 182},
{"name": "tutorial", "width": 114, "height": 98, "x": 584, "y": 182},
{"name": "white", "width": 32, "height": 32, "x": 584, "y": 448}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

View File

@ -1,77 +0,0 @@
-- ==============================================
-- FlappyBird 示例 - Xmake 构建脚本
-- 支持平台: MinGW (Windows), Nintendo Switch
-- ==============================================
-- 获取当前脚本所在目录(示例根目录)
local example_dir = os.scriptdir()
-- 可执行文件目标
target("flappy_bird")
set_kind("binary")
add_files("*.cpp")
add_includedirs("../../Extra2D/include")
add_includedirs("../../Extra2D/include/json")
add_deps("extra2d")
-- 使用与主项目相同的平台配置
if is_plat("switch") then
set_targetdir("../../build/examples/flappy_bird")
-- 构建后生成 NRO 文件
after_build(function (target)
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
local elf_file = target:targetfile()
local output_dir = path.directory(elf_file)
local nacp_file = path.join(output_dir, "flappy_bird.nacp")
local nro_file = path.join(output_dir, "flappy_bird.nro")
local nacptool = path.join(devkitPro, "tools/bin/nacptool.exe")
local elf2nro = path.join(devkitPro, "tools/bin/elf2nro.exe")
if os.isfile(nacptool) and os.isfile(elf2nro) then
os.vrunv(nacptool, {"--create", "FlappyBird", "Extra2D Team", "1.0.0", nacp_file})
local romfs = path.join(example_dir, "romfs")
if os.isdir(romfs) then
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file, "--romfsdir=" .. romfs})
else
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file})
end
print("Generated NRO: " .. nro_file)
end
end)
-- 打包时将 NRO 文件复制到 package 目录
after_package(function (target)
local nro_file = path.join(target:targetdir(), "flappy_bird.nro")
local package_dir = target:packagedir()
if os.isfile(nro_file) and package_dir then
os.cp(nro_file, package_dir)
print("Copied NRO to package: " .. package_dir)
end
end)
elseif is_plat("mingw") then
set_targetdir("../../build/examples/flappy_bird")
-- add_ldflags("-mwindows", {force = true})
-- 复制资源到输出目录
after_build(function (target)
local romfs = path.join(example_dir, "romfs")
if os.isdir(romfs) then
local target_dir = path.directory(target:targetfile())
local assets_dir = path.join(target_dir, "assets")
-- 创建 assets 目录
if not os.isdir(assets_dir) then
os.mkdir(assets_dir)
end
-- 复制所有资源文件(包括子目录)
os.cp(path.join(romfs, "assets/**"), assets_dir)
print("Copied assets from " .. romfs .. " to " .. assets_dir)
else
print("Warning: romfs directory not found at " .. romfs)
end
end)
end
target_end()

View File

@ -1,138 +0,0 @@
// ============================================================================
// BaseScene.cpp - Push Box 基础场景实现
// ============================================================================
#include "BaseScene.h"
#include <extra2d/scene/transition_scene.h>
#include <extra2d/utils/logger.h>
namespace pushbox {
BaseScene::BaseScene() {
// 设置背景颜色为黑色(窗口四周会显示这个颜色)
setBackgroundColor(extra2d::Color(0.0f, 0.0f, 0.0f, 1.0f));
}
void BaseScene::onEnter() {
extra2d::Scene::onEnter();
// 计算并更新视口
updateViewport();
}
/**
* @brief 使
*/
void BaseScene::updateViewport() {
auto &app = extra2d::Application::instance();
float windowWidth = static_cast<float>(app.window().getWidth());
float windowHeight = static_cast<float>(app.window().getHeight());
// 计算游戏内容在窗口中的居中位置
// 保持游戏原始宽高比,进行"黑边"适配
float scaleX = windowWidth / GAME_WIDTH;
float scaleY = windowHeight / GAME_HEIGHT;
// 使用较小的缩放比例,确保游戏内容完整显示在窗口中
float scale = std::min(scaleX, scaleY);
scaledGameWidth_ = GAME_WIDTH * scale;
scaledGameHeight_ = GAME_HEIGHT * scale;
// 计算居中偏移,使游戏内容在窗口中水平和垂直居中
viewportOffsetX_ = (windowWidth - scaledGameWidth_) * 0.5f;
viewportOffsetY_ = (windowHeight - scaledGameHeight_) * 0.5f;
// 设置视口大小为游戏逻辑分辨率
setViewportSize(GAME_WIDTH, GAME_HEIGHT);
// 创建并设置相机
auto camera = extra2d::makePtr<extra2d::Camera>();
// 设置正交投影,覆盖整个游戏逻辑区域
// 注意对于2D游戏Y轴向下增长所以bottom > top
camera->setViewport(0.0f, GAME_WIDTH, GAME_HEIGHT, 0.0f);
setCamera(camera);
}
/**
* @brief
* @param renderer
*/
void BaseScene::onRender(extra2d::RenderBackend &renderer) {
// 检查窗口大小是否改变,如果改变则更新视口
auto &app = extra2d::Application::instance();
float currentWindowWidth = static_cast<float>(app.window().getWidth());
float currentWindowHeight = static_cast<float>(app.window().getHeight());
// 如果窗口大小改变,重新计算视口
float expectedWidth = scaledGameWidth_ + viewportOffsetX_ * 2.0f;
float expectedHeight = scaledGameHeight_ + viewportOffsetY_ * 2.0f;
if (std::abs(currentWindowWidth - expectedWidth) > 1.0f ||
std::abs(currentWindowHeight - expectedHeight) > 1.0f) {
E2D_LOG_INFO("BaseScene::onRender - window size changed from ({} x {}) to "
"({} x {}), updating viewport",
expectedWidth, expectedHeight, currentWindowWidth,
currentWindowHeight);
updateViewport();
}
// 设置视口为居中区域
renderer.setViewport(
static_cast<int>(viewportOffsetX_), static_cast<int>(viewportOffsetY_),
static_cast<int>(scaledGameWidth_), static_cast<int>(scaledGameHeight_));
// 调用父类的 onRender 进行实际渲染
extra2d::Scene::onRender(renderer);
}
/**
* @brief
* @param renderer
*/
void BaseScene::renderContent(extra2d::RenderBackend &renderer) {
// 如果视口参数未初始化onEnter 还没被调用),先初始化
if (scaledGameWidth_ <= 0.0f || scaledGameHeight_ <= 0.0f) {
updateViewport();
}
// 检查窗口大小是否改变
auto &app = extra2d::Application::instance();
float currentWindowWidth = static_cast<float>(app.window().getWidth());
float currentWindowHeight = static_cast<float>(app.window().getHeight());
float expectedWidth = scaledGameWidth_ + viewportOffsetX_ * 2.0f;
float expectedHeight = scaledGameHeight_ + viewportOffsetY_ * 2.0f;
if (std::abs(currentWindowWidth - expectedWidth) > 1.0f ||
std::abs(currentWindowHeight - expectedHeight) > 1.0f) {
updateViewport();
}
// 检查当前场景是否作为 TransitionScene 的子场景被渲染
bool isChildOfTransition = false;
if (auto parent = getParent()) {
if (dynamic_cast<extra2d::TransitionScene *>(parent.get())) {
isChildOfTransition = true;
}
}
if (isChildOfTransition) {
// 作为 TransitionScene 的子场景时,需要设置正确的投影矩阵
// 使用游戏逻辑分辨率作为投影区域,让 TransitionScene 控制整体视口
auto camera = getActiveCamera();
if (camera) {
// 设置投影矩阵覆盖整个游戏逻辑区域
renderer.setViewProjection(camera->getViewProjectionMatrix());
}
// 渲染场景内容(投影矩阵已设置,直接渲染)
batchUpdateTransforms();
renderer.beginSpriteBatch();
render(renderer);
renderer.endSpriteBatch();
} else {
// 正常渲染时,调用父类的 renderContent 处理视口和投影
renderer.setViewport(static_cast<int>(viewportOffsetX_),
static_cast<int>(viewportOffsetY_),
static_cast<int>(scaledGameWidth_),
static_cast<int>(scaledGameHeight_));
extra2d::Scene::renderContent(renderer);
}
}
} // namespace pushbox

View File

@ -1,57 +0,0 @@
// ============================================================================
// BaseScene.h - Push Box 基础场景类
// 描述: 提供统一的居中视口适配功能,所有游戏场景都应继承此类
// ============================================================================
#pragma once
#include <extra2d/extra2d.h>
namespace pushbox {
// 游戏逻辑分辨率
static constexpr float GAME_WIDTH = 640.0f;
static constexpr float GAME_HEIGHT = 480.0f;
/**
* @brief Push Box
*
*/
class BaseScene : public extra2d::Scene {
public:
/**
* @brief
*/
BaseScene();
/**
* @brief
*/
void onEnter() override;
/**
* @brief
* @param renderer
*/
void onRender(extra2d::RenderBackend &renderer) override;
/**
* @brief
* @param renderer
*/
void renderContent(extra2d::RenderBackend &renderer) override;
protected:
/**
* @brief 使
*/
void updateViewport();
// 视口适配参数(用于在窗口中居中显示游戏内容)
float scaledGameWidth_ = 0.0f; // 缩放后的游戏宽度
float scaledGameHeight_ = 0.0f; // 缩放后的游戏高度
float viewportOffsetX_ = 0.0f; // 视口水平偏移
float viewportOffsetY_ = 0.0f; // 视口垂直偏移
};
} // namespace pushbox

View File

@ -1,487 +0,0 @@
// ============================================================================
// PlayScene.cpp - Push Box 游戏场景实现
// ============================================================================
#include "PlayScene.h"
#include "StartScene.h"
#include "SuccessScene.h"
#include "audio_manager.h"
#include "storage.h"
#include <extra2d/extra2d.h>
#include <extra2d/utils/object_pool.h>
namespace pushbox {
/**
* @brief
* @param size
*/
static extra2d::Ptr<extra2d::FontAtlas> loadFont(int size) {
auto &resources = extra2d::Application::instance().resources();
auto font = resources.loadFont("assets/font.ttf", size);
return font;
}
PlayScene::PlayScene(int level) : BaseScene() {
auto &app = extra2d::Application::instance();
auto &resources = app.resources();
E2D_LOG_INFO("PlayScene: Loading textures...");
texWall_ = resources.loadTexture("assets/images/wall.gif");
texPoint_ = resources.loadTexture("assets/images/point.gif");
texFloor_ = resources.loadTexture("assets/images/floor.gif");
texBox_ = resources.loadTexture("assets/images/box.gif");
texBoxInPoint_ = resources.loadTexture("assets/images/boxinpoint.gif");
texMan_[1] = resources.loadTexture("assets/images/player/manup.gif");
texMan_[2] = resources.loadTexture("assets/images/player/mandown.gif");
texMan_[3] = resources.loadTexture("assets/images/player/manleft.gif");
texMan_[4] = resources.loadTexture("assets/images/player/manright.gif");
texManPush_[1] = resources.loadTexture("assets/images/player/manhandup.gif");
texManPush_[2] =
resources.loadTexture("assets/images/player/manhanddown.gif");
texManPush_[3] =
resources.loadTexture("assets/images/player/manhandleft.gif");
texManPush_[4] =
resources.loadTexture("assets/images/player/manhandright.gif");
font28_ = loadFont(28);
font20_ = loadFont(20);
// 使用游戏逻辑分辨率
float screenW = GAME_WIDTH;
float screenH = GAME_HEIGHT;
// 计算游戏区域居中偏移
float offsetX = (screenW - GAME_WIDTH) / 2.0f;
float offsetY = (screenH - GAME_HEIGHT) / 2.0f;
// 音效开关按钮(使用 Button 的切换模式)
auto soundOn = resources.loadTexture("assets/images/soundon.png");
auto soundOff = resources.loadTexture("assets/images/soundoff.png");
if (soundOn && soundOff) {
soundBtn_ = extra2d::Button::create();
soundBtn_->setToggleMode(true);
soundBtn_->setStateBackgroundImage(soundOff, soundOn);
soundBtn_->setOn(g_SoundOpen);
soundBtn_->setAnchor(0.0f, 0.0f);
soundBtn_->setPosition(offsetX + 50.0f, offsetY + 50.0f);
soundBtn_->setOnStateChange([](bool isOn) {
g_SoundOpen = isOn;
AudioManager::instance().setEnabled(isOn);
});
addChild(soundBtn_);
}
levelText_ = extra2d::Text::create("", font28_);
levelText_->setPosition(offsetX + 520.0f, offsetY + 30.0f);
levelText_->setTextColor(extra2d::Colors::White);
addChild(levelText_);
stepText_ = extra2d::Text::create("", font20_);
stepText_->setPosition(offsetX + 520.0f, offsetY + 100.0f);
stepText_->setTextColor(extra2d::Colors::White);
addChild(stepText_);
bestText_ = extra2d::Text::create("", font20_);
bestText_->setPosition(offsetX + 520.0f, offsetY + 140.0f);
bestText_->setTextColor(extra2d::Colors::White);
addChild(bestText_);
// 创建菜单文本(使用颜色变化指示选中)
restartText_ = extra2d::Text::create("Y键重开", font20_);
restartText_->setPosition(offsetX + 520.0f, offsetY + 290.0f);
addChild(restartText_);
soundToggleText_ = extra2d::Text::create("X键切换音效", font20_);
soundToggleText_->setPosition(offsetX + 520.0f, offsetY + 330.0f);
addChild(soundToggleText_);
// 撤销提示(对象池使用示例)
undoText_ = extra2d::Text::create("Z键撤销", font20_);
undoText_->setPosition(offsetX + 520.0f, offsetY + 370.0f);
addChild(undoText_);
mapLayer_ = extra2d::makePtr<extra2d::Node>();
mapLayer_->setAnchor(0.0f, 0.0f);
mapLayer_->setPosition(0.0f, 0.0f);
addChild(mapLayer_);
setLevel(level);
}
void PlayScene::onEnter() {
BaseScene::onEnter();
if (soundBtn_) {
soundBtn_->setOn(g_SoundOpen);
}
updateMenuColors();
}
/**
* @brief
*/
void PlayScene::updateMenuColors() {
// 选中的项用红色,未选中的用白色
if (restartText_) {
restartText_->setTextColor(menuIndex_ == 0 ? extra2d::Colors::Red
: extra2d::Colors::White);
}
if (soundToggleText_) {
soundToggleText_->setTextColor(menuIndex_ == 1 ? extra2d::Colors::Red
: extra2d::Colors::White);
}
if (undoText_) {
undoText_->setTextColor(menuIndex_ == 2 ? extra2d::Colors::Red
: extra2d::Colors::White);
}
}
void PlayScene::onUpdate(float dt) {
BaseScene::onUpdate(dt);
auto &app = extra2d::Application::instance();
auto &input = app.input();
// B 键返回主菜单
if (input.isButtonPressed(extra2d::GamepadButton::B)) {
app.scenes().replaceScene(extra2d::makePtr<StartScene>(),
extra2d::TransitionType::Fade, 0.5f);
return;
}
// Y 键重开
if (input.isButtonPressed(extra2d::GamepadButton::Y)) {
setLevel(g_CurrentLevel);
return;
}
// X键直接切换音效备用按钮也可点击切换
if (input.isButtonPressed(extra2d::GamepadButton::X)) {
g_SoundOpen = !g_SoundOpen;
AudioManager::instance().setEnabled(g_SoundOpen);
if (soundBtn_) {
soundBtn_->setOn(g_SoundOpen);
}
return;
}
// Z 键撤销(对象池使用示例)
if (input.isKeyPressed(extra2d::Key::Z)) {
undoMove();
return;
}
// A 键执行选中的菜单项
if (input.isButtonPressed(extra2d::GamepadButton::A)) {
executeMenuItem();
return;
}
// 方向键移动
if (input.isButtonPressed(extra2d::GamepadButton::DPadUp)) {
move(0, -1, 1);
flush();
} else if (input.isButtonPressed(extra2d::GamepadButton::DPadDown)) {
move(0, 1, 2);
flush();
} else if (input.isButtonPressed(extra2d::GamepadButton::DPadLeft)) {
move(-1, 0, 3);
flush();
} else if (input.isButtonPressed(extra2d::GamepadButton::DPadRight)) {
move(1, 0, 4);
flush();
} else {
return;
}
// 检查是否通关
for (int i = 0; i < map_.width; i++) {
for (int j = 0; j < map_.height; j++) {
Piece p = map_.value[j][i];
if (p.type == TYPE::Box && p.isPoint == false) {
return;
}
}
}
gameOver();
}
/**
* @brief
*/
void PlayScene::executeMenuItem() {
switch (menuIndex_) {
case 0: // 重开
setLevel(g_CurrentLevel);
break;
case 1: // 切换音效
g_SoundOpen = !g_SoundOpen;
AudioManager::instance().setEnabled(g_SoundOpen);
if (soundBtn_) {
soundBtn_->setOn(g_SoundOpen);
}
break;
case 2: // 撤销
undoMove();
break;
}
}
/**
* @brief
*/
void PlayScene::flush() {
mapLayer_->removeAllChildren();
int tileW = texFloor_ ? texFloor_->getWidth() : 32;
int tileH = texFloor_ ? texFloor_->getHeight() : 32;
// 使用游戏逻辑分辨率
float gameWidth = GAME_WIDTH;
float gameHeight = GAME_HEIGHT;
float baseOffsetX = 0.0f;
float baseOffsetY = 0.0f;
// 在 12x12 网格中居中地图
float mapOffsetX = static_cast<float>((12.0f - map_.width) / 2.0f) * tileW;
float mapOffsetY = static_cast<float>((12.0f - map_.height) / 2.0f) * tileH;
float offsetX = baseOffsetX + mapOffsetX;
float offsetY = baseOffsetY + mapOffsetY;
for (int i = 0; i < map_.width; i++) {
for (int j = 0; j < map_.height; j++) {
Piece piece = map_.value[j][i];
extra2d::Ptr<extra2d::Texture> tex;
if (piece.type == TYPE::Wall) {
tex = texWall_;
} else if (piece.type == TYPE::Ground && piece.isPoint) {
tex = texPoint_;
} else if (piece.type == TYPE::Ground) {
tex = texFloor_;
} else if (piece.type == TYPE::Box && piece.isPoint) {
tex = texBoxInPoint_;
} else if (piece.type == TYPE::Box) {
tex = texBox_;
} else if (piece.type == TYPE::Man && g_Pushing) {
tex = texManPush_[g_Direct];
} else if (piece.type == TYPE::Man) {
tex = texMan_[g_Direct];
} else {
continue;
}
if (!tex) {
continue;
}
auto sprite = extra2d::Sprite::create(tex);
sprite->setAnchor(0.0f, 0.0f);
sprite->setPosition(offsetX + static_cast<float>(i * tileW),
offsetY + static_cast<float>(j * tileH));
mapLayer_->addChild(sprite);
}
}
}
/**
* @brief
* @param level
*/
void PlayScene::setLevel(int level) {
g_CurrentLevel = level;
saveCurrentLevel(g_CurrentLevel);
// 清空移动历史(智能指针自动回收到对象池)
while (!moveHistory_.empty()) {
moveHistory_.pop();
}
if (levelText_) {
levelText_->setText("" + std::to_string(level) + "");
}
setStep(0);
int bestStep = loadBestStep(level, 0);
if (bestText_) {
if (bestStep != 0) {
bestText_->setText("最佳" + std::to_string(bestStep) + "");
} else {
bestText_->setText("");
}
}
// 深拷贝地图数据
Map &sourceMap = g_Maps[level - 1];
map_.width = sourceMap.width;
map_.height = sourceMap.height;
map_.roleX = sourceMap.roleX;
map_.roleY = sourceMap.roleY;
for (int i = 0; i < 12; i++) {
for (int j = 0; j < 12; j++) {
map_.value[i][j] = sourceMap.value[i][j];
}
}
g_Direct = 2;
g_Pushing = false;
flush();
}
/**
* @brief
* @param step
*/
void PlayScene::setStep(int step) {
step_ = step;
if (stepText_) {
stepText_->setText("当前" + std::to_string(step) + "");
}
}
/**
* @brief
* @param dx X方向偏移
* @param dy Y方向偏移
* @param direct 1=2=3=4=
*/
void PlayScene::move(int dx, int dy, int direct) {
int targetX = dx + map_.roleX;
int targetY = dy + map_.roleY;
g_Direct = direct;
if (targetX < 0 || targetX >= map_.width || targetY < 0 ||
targetY >= map_.height) {
return;
}
if (map_.value[targetY][targetX].type == TYPE::Wall) {
return;
}
// 使用对象池创建移动记录(自动管理内存)
auto record = E2D_MAKE_POOLED(MoveRecord, map_.roleX, map_.roleY, targetX, targetY, false);
if (map_.value[targetY][targetX].type == TYPE::Ground) {
g_Pushing = false;
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
map_.value[targetY][targetX].type = TYPE::Man;
AudioManager::instance().playManMove();
} else if (map_.value[targetY][targetX].type == TYPE::Box) {
g_Pushing = true;
int boxX = 0;
int boxY = 0;
switch (g_Direct) {
case 1:
boxX = targetX;
boxY = targetY - 1;
break;
case 2:
boxX = targetX;
boxY = targetY + 1;
break;
case 3:
boxX = targetX - 1;
boxY = targetY;
break;
case 4:
boxX = targetX + 1;
boxY = targetY;
break;
default:
return;
}
if (boxX < 0 || boxX >= map_.width || boxY < 0 || boxY >= map_.height) {
return;
}
if (map_.value[boxY][boxX].type == TYPE::Wall ||
map_.value[boxY][boxX].type == TYPE::Box) {
return;
}
// 记录箱子移动
record->pushedBox = true;
record->boxFromX = targetX;
record->boxFromY = targetY;
record->boxToX = boxX;
record->boxToY = boxY;
map_.value[boxY][boxX].type = TYPE::Box;
map_.value[targetY][targetX].type = TYPE::Man;
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
AudioManager::instance().playBoxMove();
} else {
return;
}
// 保存移动记录到历史栈
moveHistory_.push(record);
map_.roleX = targetX;
map_.roleY = targetY;
setStep(step_ + 1);
}
/**
* @brief
*/
void PlayScene::gameOver() {
int bestStep = loadBestStep(g_CurrentLevel, 0);
if (bestStep == 0 || step_ < bestStep) {
saveBestStep(g_CurrentLevel, step_);
}
if (g_CurrentLevel == MAX_LEVEL) {
extra2d::Application::instance().scenes().pushScene(
extra2d::makePtr<SuccessScene>(), extra2d::TransitionType::Fade, 0.5f);
return;
}
setLevel(g_CurrentLevel + 1);
}
/**
* @brief 使
*
*/
void PlayScene::undoMove() {
if (moveHistory_.empty()) {
E2D_LOG_INFO("No moves to undo");
return;
}
auto record = moveHistory_.top();
moveHistory_.pop();
// 恢复玩家位置
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
map_.value[record->fromY][record->fromX].type = TYPE::Man;
map_.roleX = record->fromX;
map_.roleY = record->fromY;
// 如果推了箱子,恢复箱子位置
if (record->pushedBox) {
map_.value[record->boxToY][record->boxToX].type = TYPE::Ground;
map_.value[record->boxFromY][record->boxFromX].type = TYPE::Box;
}
// record 智能指针离开作用域后自动回收到对象池
setStep(step_ - 1);
flush();
E2D_LOG_INFO("Undo move, step: {}", step_);
}
} // namespace pushbox

View File

@ -1,113 +0,0 @@
// ============================================================================
// PlayScene.h - Push Box 游戏场景
// ============================================================================
#pragma once
#include "BaseScene.h"
#include "data.h"
#include <extra2d/extra2d.h>
#include <extra2d/utils/object_pool.h>
#include <stack>
namespace pushbox {
/**
* @brief Push Box
*/
class PlayScene : public BaseScene {
public:
/**
* @brief
* @param level
*/
explicit PlayScene(int level);
/**
* @brief
*/
void onEnter() override;
/**
* @brief
* @param dt
*/
void onUpdate(float dt) override;
private:
/**
* @brief
*/
void updateMenuColors();
/**
* @brief
*/
void executeMenuItem();
/**
* @brief
*/
void flush();
/**
* @brief
* @param level
*/
void setLevel(int level);
/**
* @brief
* @param step
*/
void setStep(int step);
/**
* @brief
* @param dx X方向偏移
* @param dy Y方向偏移
* @param direct 1=2=3=4=
*/
void move(int dx, int dy, int direct);
/**
* @brief
*/
void gameOver();
/**
* @brief 使
*/
void undoMove();
int step_ = 0;
int menuIndex_ = 0;
Map map_{};
extra2d::Ptr<extra2d::FontAtlas> font28_;
extra2d::Ptr<extra2d::FontAtlas> font20_;
extra2d::Ptr<extra2d::Text> levelText_;
extra2d::Ptr<extra2d::Text> stepText_;
extra2d::Ptr<extra2d::Text> bestText_;
extra2d::Ptr<extra2d::Text> restartText_;
extra2d::Ptr<extra2d::Text> soundToggleText_;
extra2d::Ptr<extra2d::Text> undoText_;
extra2d::Ptr<extra2d::Node> mapLayer_;
extra2d::Ptr<extra2d::Button> soundBtn_;
extra2d::Ptr<extra2d::Texture> texWall_;
extra2d::Ptr<extra2d::Texture> texPoint_;
extra2d::Ptr<extra2d::Texture> texFloor_;
extra2d::Ptr<extra2d::Texture> texBox_;
extra2d::Ptr<extra2d::Texture> texBoxInPoint_;
extra2d::Ptr<extra2d::Texture> texMan_[5];
extra2d::Ptr<extra2d::Texture> texManPush_[5];
// 对象池使用示例:使用智能指针管理 MoveRecord
std::stack<extra2d::Ptr<MoveRecord>> moveHistory_;
};
} // namespace pushbox

View File

@ -1,227 +0,0 @@
// ============================================================================
// StartScene.cpp - Push Box 开始场景实现
// ============================================================================
#include "StartScene.h"
#include "PlayScene.h"
#include "audio_manager.h"
#include "data.h"
#include <extra2d/extra2d.h>
namespace pushbox {
StartScene::StartScene() : BaseScene() {
// BaseScene 已处理视口设置
}
/**
* @brief
*/
static extra2d::Ptr<extra2d::FontAtlas> loadMenuFont() {
auto &resources = extra2d::Application::instance().resources();
auto font = resources.loadFont("assets/font.ttf", 28, true);
return font;
}
void StartScene::onEnter() {
BaseScene::onEnter();
auto &app = extra2d::Application::instance();
auto &resources = app.resources();
if (getChildren().empty()) {
// 使用游戏逻辑分辨率
float screenW = GAME_WIDTH;
float screenH = GAME_HEIGHT;
auto bgTex = resources.loadTexture("assets/images/start.jpg");
if (bgTex) {
auto background = extra2d::Sprite::create(bgTex);
float bgWidth = static_cast<float>(bgTex->getWidth());
float bgHeight = static_cast<float>(bgTex->getHeight());
float offsetX = (screenW - bgWidth) / 2.0f;
float offsetY = (screenH - bgHeight) / 2.0f;
background->setAnchor(0.0f, 0.0f);
background->setPosition(offsetX, offsetY);
addChild(background);
float centerX = screenW / 2.0f;
font_ = loadMenuFont();
if (!font_) {
return;
}
// 创建菜单按钮(使用 Button 实现文本居中)
startBtn_ = extra2d::Button::create();
startBtn_->setFont(font_);
startBtn_->setText("新游戏");
startBtn_->setTextColor(extra2d::Colors::Black);
startBtn_->setBackgroundColor(extra2d::Colors::Transparent,
extra2d::Colors::Transparent,
extra2d::Colors::Transparent);
startBtn_->setBorder(extra2d::Colors::Transparent, 0.0f);
startBtn_->setPadding(extra2d::Vec2(0.0f, 0.0f));
startBtn_->setCustomSize(200.0f, 40.0f);
startBtn_->setAnchor(0.5f, 0.5f);
startBtn_->setPosition(centerX, offsetY + 260.0f);
addChild(startBtn_);
resumeBtn_ = extra2d::Button::create();
resumeBtn_->setFont(font_);
resumeBtn_->setText("继续关卡");
resumeBtn_->setTextColor(extra2d::Colors::Black);
resumeBtn_->setBackgroundColor(extra2d::Colors::Transparent,
extra2d::Colors::Transparent,
extra2d::Colors::Transparent);
resumeBtn_->setBorder(extra2d::Colors::Transparent, 0.0f);
resumeBtn_->setPadding(extra2d::Vec2(0.0f, 0.0f));
resumeBtn_->setCustomSize(200.0f, 40.0f);
resumeBtn_->setAnchor(0.5f, 0.5f);
resumeBtn_->setPosition(centerX, offsetY + 300.0f);
addChild(resumeBtn_);
exitBtn_ = extra2d::Button::create();
exitBtn_->setFont(font_);
exitBtn_->setText("退出");
exitBtn_->setTextColor(extra2d::Colors::Black);
exitBtn_->setBackgroundColor(extra2d::Colors::Transparent,
extra2d::Colors::Transparent,
extra2d::Colors::Transparent);
exitBtn_->setBorder(extra2d::Colors::Transparent, 0.0f);
exitBtn_->setPadding(extra2d::Vec2(0.0f, 0.0f));
exitBtn_->setCustomSize(200.0f, 40.0f);
exitBtn_->setAnchor(0.5f, 0.5f);
exitBtn_->setPosition(centerX, offsetY + 340.0f);
addChild(exitBtn_);
// 音效开关按钮(使用 Button 的切换模式)
auto soundOn = resources.loadTexture("assets/images/soundon.png");
auto soundOff = resources.loadTexture("assets/images/soundoff.png");
if (soundOn && soundOff) {
soundBtn_ = extra2d::Button::create();
soundBtn_->setToggleMode(true);
soundBtn_->setStateBackgroundImage(soundOff, soundOn);
soundBtn_->setOn(g_SoundOpen);
soundBtn_->setAnchor(0.0f, 0.0f);
soundBtn_->setPosition(offsetX + 50.0f, offsetY + 50.0f);
soundBtn_->setOnStateChange([](bool isOn) {
g_SoundOpen = isOn;
AudioManager::instance().setEnabled(isOn);
});
addChild(soundBtn_);
}
}
}
// 始终有3个菜单项
menuCount_ = 3;
updateMenuColors();
}
void StartScene::onUpdate(float dt) {
BaseScene::onUpdate(dt);
auto &app = extra2d::Application::instance();
auto &input = app.input();
// 方向键上下切换选择
if (input.isButtonPressed(extra2d::GamepadButton::DPadUp)) {
selectedIndex_ = (selectedIndex_ - 1 + menuCount_) % menuCount_;
updateMenuColors();
} else if (input.isButtonPressed(extra2d::GamepadButton::DPadDown)) {
selectedIndex_ = (selectedIndex_ + 1) % menuCount_;
updateMenuColors();
}
// A键确认
if (input.isButtonPressed(extra2d::GamepadButton::A)) {
executeMenuItem();
}
// X键切换音效备用按钮也可点击切换
if (input.isButtonPressed(extra2d::GamepadButton::X)) {
g_SoundOpen = !g_SoundOpen;
AudioManager::instance().setEnabled(g_SoundOpen);
if (soundBtn_) {
soundBtn_->setOn(g_SoundOpen);
}
}
}
/**
* @brief
*/
void StartScene::updateMenuColors() {
// 根据选中状态更新按钮文本颜色
// 选中的项用红色,未选中的用黑色,禁用的项用深灰色
if (startBtn_) {
startBtn_->setTextColor(selectedIndex_ == 0 ? extra2d::Colors::Red
: extra2d::Colors::Black);
}
if (resumeBtn_) {
// "继续关卡"始终显示,但当 g_CurrentLevel == 1 时禁用(深灰色)
if (g_CurrentLevel > 1) {
// 可用状态:选中为红色,未选中为黑色
resumeBtn_->setTextColor(selectedIndex_ == 1 ? extra2d::Colors::Red
: extra2d::Colors::Black);
} else {
// 禁用状态:深灰色 (RGB: 80, 80, 80)
resumeBtn_->setTextColor(extra2d::Color(80, 80, 80, 255));
}
}
if (exitBtn_) {
exitBtn_->setTextColor(selectedIndex_ == 2 ? extra2d::Colors::Red
: extra2d::Colors::Black);
}
}
/**
* @brief
*/
void StartScene::executeMenuItem() {
// 始终有3个选项但"继续关卡"(索引1)在 g_CurrentLevel == 1 时禁用
switch (selectedIndex_) {
case 0:
startNewGame();
break;
case 1:
// 只有当 g_CurrentLevel > 1 时才能选择"继续关卡"
if (g_CurrentLevel > 1) {
continueGame();
}
break;
case 2:
exitGame();
break;
}
}
/**
* @brief
*/
void StartScene::startNewGame() {
extra2d::Application::instance().scenes().replaceScene(
extra2d::makePtr<PlayScene>(1), extra2d::TransitionType::Fade, 0.5f);
}
/**
* @brief
*/
void StartScene::continueGame() {
extra2d::Application::instance().scenes().replaceScene(
extra2d::makePtr<PlayScene>(g_CurrentLevel),
extra2d::TransitionType::Fade, 0.5f);
}
/**
* @brief 退
*/
void StartScene::exitGame() { extra2d::Application::instance().quit(); }
} // namespace pushbox

Some files were not shown because too many files have changed in this diff Show More