feat: 添加推箱子游戏资源文件和基础功能

添加推箱子游戏所需的资源文件,包括字体、图片和音效资源。实现音频控制器、场景管理和UI组件等基础功能。重构部分代码以适配Extra2D框架,包括:
- 音频控制逻辑
- 场景切换功能
- 游戏数据存储
- 动画系统基础
- 脚本绑定支持
- 事件系统
- 日志和资源管理

同时新增推箱子游戏的核心逻辑和场景:
- 开始场景
- 游戏场景
- 成功场景
- 游戏数据管理
- 关卡逻辑
This commit is contained in:
ChestnutYueyue 2026-02-09 12:50:59 +08:00
parent 87cd46a403
commit 787a5688e2
325 changed files with 22903 additions and 22646 deletions

View File

@ -1,14 +0,0 @@
#include "audio_context.h"
namespace pushbox {
static easy2d::WeakPtr<AudioController> g_audioController;
void setAudioController(const easy2d::Ptr<AudioController>& controller) {
g_audioController = controller;
}
easy2d::Ptr<AudioController> getAudioController() { return g_audioController.lock(); }
} // namespace pushbox

View File

@ -1,13 +0,0 @@
#pragma once
#include <easy2d/easy2d.h>
namespace pushbox {
class AudioController;
void setAudioController(const easy2d::Ptr<AudioController>& controller);
easy2d::Ptr<AudioController> getAudioController();
} // namespace pushbox

View File

@ -1,45 +0,0 @@
#pragma once
#include "../core/data.h"
#include <easy2d/easy2d.h>
namespace pushbox {
class PlayScene : public easy2d::Scene {
public:
explicit PlayScene(int level);
void onEnter() override;
void onUpdate(float dt) override;
private:
void flush();
void setLevel(int level);
void setStep(int step);
void move(int dx, int dy, int direct);
void gameOver();
int step_ = 0;
Map map_{};
easy2d::Ptr<easy2d::FontAtlas> font28_;
easy2d::Ptr<easy2d::FontAtlas> font20_;
easy2d::Ptr<easy2d::Text> levelText_;
easy2d::Ptr<easy2d::Text> stepText_;
easy2d::Ptr<easy2d::Text> bestText_;
easy2d::Ptr<easy2d::Node> mapLayer_;
easy2d::Ptr<easy2d::ToggleImageButton> soundBtn_;
easy2d::Ptr<easy2d::Texture> texWall_;
easy2d::Ptr<easy2d::Texture> texPoint_;
easy2d::Ptr<easy2d::Texture> texFloor_;
easy2d::Ptr<easy2d::Texture> texBox_;
easy2d::Ptr<easy2d::Texture> texBoxInPoint_;
easy2d::Ptr<easy2d::Texture> texMan_[5];
easy2d::Ptr<easy2d::Texture> texManPush_[5];
};
} // namespace pushbox

View File

@ -1,56 +0,0 @@
#include "success_scene.h"
#include "../ui/menu_button.h"
#include <easy2d/app/application.h>
#include <easy2d/resource/resource_manager.h>
#include <easy2d/scene/scene_manager.h>
#include <easy2d/scene/sprite.h>
namespace pushbox {
SuccessScene::SuccessScene() {
// 设置视口大小为窗口尺寸
auto& app = easy2d::Application::instance();
auto& config = app.getConfig();
setViewportSize(static_cast<float>(config.width), static_cast<float>(config.height));
}
static easy2d::Ptr<easy2d::FontAtlas> loadMenuFont() {
auto& resources = easy2d::Application::instance().resources();
return resources.loadFont("assets/font.ttf", 28);
}
void SuccessScene::onEnter() {
Scene::onEnter();
auto& app = easy2d::Application::instance();
auto& resources = app.resources();
setBackgroundColor(easy2d::Colors::Black);
if (getChildren().empty()) {
auto bgTex = resources.loadTexture("assets/images/success.jpg");
if (bgTex) {
auto background = easy2d::Sprite::create(bgTex);
background->setAnchor(0.0f, 0.0f);
background->setPosition(0.0f, 0.0f);
float sx = static_cast<float>(app.getConfig().width) / static_cast<float>(bgTex->getWidth());
float sy =
static_cast<float>(app.getConfig().height) / static_cast<float>(bgTex->getHeight());
background->setScale(sx, sy);
addChild(background);
}
auto font = loadMenuFont();
if (font) {
auto backBtn = MenuButton::create(font, "回主菜单", []() {
auto& scenes = easy2d::Application::instance().scenes();
scenes.popScene(easy2d::TransitionType::Fade, 0.2f);
scenes.popScene(easy2d::TransitionType::Fade, 0.2f);
});
backBtn->setPosition(app.getConfig().width / 2.0f, 350.0f);
addChild(backBtn);
}
}
}
} // namespace pushbox

View File

@ -1,58 +0,0 @@
#include "menu_button.h"
#include <easy2d/core/color.h>
#include <easy2d/event/event.h>
namespace pushbox {
easy2d::Ptr<MenuButton> MenuButton::create(easy2d::Ptr<easy2d::FontAtlas> font,
const easy2d::String& text,
easy2d::Function<void()> onClick) {
auto btn = easy2d::makePtr<MenuButton>();
btn->setFont(font);
btn->setText(text);
btn->setPadding(easy2d::Vec2(0.0f, 0.0f));
btn->setBackgroundColor(easy2d::Colors::Transparent, easy2d::Colors::Transparent,
easy2d::Colors::Transparent);
btn->setBorder(easy2d::Colors::Transparent, 0.0f);
btn->setTextColor(easy2d::Colors::Black);
btn->onClick_ = std::move(onClick);
btn->setOnClick([wbtn = easy2d::WeakPtr<MenuButton>(btn)]() {
if (auto self = wbtn.lock()) {
if (self->enabled_ && self->onClick_) {
self->onClick_();
}
}
});
btn->getEventDispatcher().addListener(
easy2d::EventType::UIHoverEnter,
[wbtn = easy2d::WeakPtr<MenuButton>(btn)](easy2d::Event&) {
if (auto self = wbtn.lock()) {
if (self->enabled_) {
self->setTextColor(easy2d::Colors::Blue);
}
}
});
btn->getEventDispatcher().addListener(
easy2d::EventType::UIHoverExit,
[wbtn = easy2d::WeakPtr<MenuButton>(btn)](easy2d::Event&) {
if (auto self = wbtn.lock()) {
if (self->enabled_) {
self->setTextColor(easy2d::Colors::Black);
}
}
});
return btn;
}
void MenuButton::setEnabled(bool enabled) {
enabled_ = enabled;
setTextColor(enabled ? easy2d::Colors::Black : easy2d::Colors::LightGray);
}
} // namespace pushbox

View File

@ -1,21 +0,0 @@
#pragma once
#include <easy2d/easy2d.h>
namespace pushbox {
class MenuButton : public easy2d::Button {
public:
static easy2d::Ptr<MenuButton> create(easy2d::Ptr<easy2d::FontAtlas> font,
const easy2d::String& text,
easy2d::Function<void()> onClick);
void setEnabled(bool enabled);
bool isEnabled() const { return enabled_; }
private:
bool enabled_ = true;
easy2d::Function<void()> onClick_;
};
} // namespace pushbox

View File

@ -1,79 +0,0 @@
#pragma once
#include <functional>
#include <memory>
namespace easy2d {
class Node;
enum class ActionState
{
Idle,
Running,
Paused,
Completed
};
class Action
{
public:
using ProgressCallback = std::function<void(float)>;
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;
virtual void start(Node* target);
virtual void stop();
virtual void update(float dt);
virtual void step(float dt);
virtual bool isDone() const = 0;
virtual Action* clone() const = 0;
virtual Action* reverse() const = 0;
void pause();
void resume();
void restart();
ActionState getState() const { return state_; }
float getElapsed() const { return elapsed_; }
float getDuration() const { return duration_; }
Node* getTarget() const { return target_; }
Node* getOriginalTarget() const { return originalTarget_; }
void setDuration(float duration) { duration_ = duration; }
void setSpeed(float speed) { speed_ = speed; }
float getSpeed() const { return speed_; }
void setProgressCallback(ProgressCallback callback) { progressCallback_ = std::move(callback); }
void setCompletionCallback(CompletionCallback callback) { completionCallback_ = std::move(callback); }
void setTag(int tag) { tag_ = tag; }
int getTag() const { return tag_; }
protected:
virtual void onStart() {}
virtual void onUpdate(float progress) = 0;
virtual void onComplete() {}
void setDone() { state_ = ActionState::Completed; }
Node* target_ = nullptr;
Node* originalTarget_ = nullptr;
ActionState state_ = ActionState::Idle;
float elapsed_ = 0.0f;
float duration_ = 0.0f;
float speed_ = 1.0f;
int tag_ = -1;
ProgressCallback progressCallback_;
CompletionCallback completionCallback_;
};
using ActionPtr = std::unique_ptr<Action>;
}

View File

@ -1,286 +0,0 @@
#pragma once
#include "easy2d/action/action.h"
#include "easy2d/core/math_types.h"
#include <vector>
#include <functional>
namespace easy2d {
// Interval Action Base
class IntervalAction : public Action
{
public:
explicit IntervalAction(float duration);
bool isDone() const override;
};
// Instant Action Base
class InstantAction : public Action
{
public:
InstantAction();
bool isDone() const override;
};
// Move Actions
class MoveBy : public IntervalAction
{
public:
MoveBy(float duration, const Vec2& delta);
Action* clone() const override;
Action* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
private:
Vec2 delta_;
Vec2 startPosition_;
};
class MoveTo : public IntervalAction
{
public:
MoveTo(float duration, const Vec2& position);
Action* clone() const override;
Action* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
private:
Vec2 endPosition_;
Vec2 startPosition_;
Vec2 delta_;
};
// Scale Actions
class ScaleBy : public IntervalAction
{
public:
ScaleBy(float duration, float scale);
ScaleBy(float duration, float scaleX, float scaleY);
ScaleBy(float duration, const Vec2& scale);
Action* clone() const override;
Action* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
private:
Vec2 deltaScale_;
Vec2 startScale_;
};
class ScaleTo : public IntervalAction
{
public:
ScaleTo(float duration, float scale);
ScaleTo(float duration, float scaleX, float scaleY);
ScaleTo(float duration, const Vec2& scale);
Action* clone() const override;
Action* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
private:
Vec2 endScale_;
Vec2 startScale_;
Vec2 delta_;
};
// Rotate Actions
class RotateBy : public IntervalAction
{
public:
RotateBy(float duration, float deltaAngle);
RotateBy(float duration, float deltaAngleX, float deltaAngleY);
Action* clone() const override;
Action* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
private:
float deltaAngle_ = 0.0f;
float startAngle_ = 0.0f;
};
class RotateTo : public IntervalAction
{
public:
RotateTo(float duration, float angle);
RotateTo(float duration, float angleX, float angleY);
Action* clone() const override;
Action* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
private:
float endAngle_ = 0.0f;
float startAngle_ = 0.0f;
float deltaAngle_ = 0.0f;
};
// Fade Actions
class FadeIn : public IntervalAction
{
public:
explicit FadeIn(float duration);
Action* clone() const override;
Action* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
private:
float startOpacity_ = 0.0f;
};
class FadeOut : public IntervalAction
{
public:
explicit FadeOut(float duration);
Action* clone() const override;
Action* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
private:
float startOpacity_ = 0.0f;
};
class FadeTo : public IntervalAction
{
public:
FadeTo(float duration, float opacity);
Action* clone() const override;
Action* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
private:
float endOpacity_ = 0.0f;
float startOpacity_ = 0.0f;
float deltaOpacity_ = 0.0f;
};
// Composite Actions
class Sequence : public IntervalAction
{
public:
Sequence(const std::vector<Action*>& actions);
~Sequence();
Action* clone() const override;
Action* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
private:
std::vector<Action*> actions_;
int currentIndex_ = 0;
float split_ = 0.0f;
float last_ = 0.0f;
};
class Spawn : public IntervalAction
{
public:
Spawn(const std::vector<Action*>& actions);
~Spawn();
Action* clone() const override;
Action* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
private:
std::vector<Action*> actions_;
};
// Loop Action
class Loop : public Action
{
public:
Loop(Action* action, int times = -1);
~Loop();
bool isDone() const override;
Action* clone() const override;
Action* reverse() const override;
protected:
void onStart() override;
void onUpdate(float progress) override;
private:
Action* action_ = nullptr;
int times_ = 1;
int currentTimes_ = 0;
};
// Delay Action
class Delay : public IntervalAction
{
public:
explicit Delay(float duration);
Action* clone() const override;
Action* reverse() const override;
protected:
void onUpdate(float progress) override;
};
// CallFunc Action
class CallFunc : public InstantAction
{
public:
using Callback = std::function<void()>;
explicit CallFunc(Callback callback);
Action* clone() const override;
Action* reverse() const override;
protected:
void onUpdate(float progress) override;
private:
Callback callback_;
};
// Helper functions
inline Sequence* sequence(const std::vector<Action*>& actions) { return new Sequence(actions); }
inline Spawn* spawn(const std::vector<Action*>& actions) { return new Spawn(actions); }
inline Loop* loop(Action* action, int times = -1) { return new Loop(action, times); }
inline Delay* delay(float duration) { return new Delay(duration); }
inline CallFunc* callFunc(CallFunc::Callback callback) { return new CallFunc(std::move(callback)); }
}

View File

@ -1,68 +0,0 @@
#pragma once
namespace easy2d {
// Easing function type
using EaseFunction = float (*)(float);
// Linear (no easing)
float easeLinear(float t);
// Quadratic
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);
// Quartic
float easeInQuart(float t);
float easeOutQuart(float t);
float easeInOutQuart(float t);
// Quintic
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);
// Ease Action wrapper
class Action;
class EaseAction
{
public:
static Action* create(Action* action, EaseFunction easeFunc);
};
}

View File

@ -1,54 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/core/math_types.h>
#include <string>
#include <vector>
namespace easy2d {
// ============================================================================
// 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 easy2d

View File

@ -1,70 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/animation/ani_parser.h>
#include <easy2d/animation/animation_cache.h>
#include <string>
#include <functional>
#include <cstdint>
namespace easy2d {
// ============================================================================
// 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 easy2d

View File

@ -1,52 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/animation/animation_clip.h>
#include <easy2d/animation/animation_cache.h>
#include <string>
#include <functional>
namespace easy2d {
// ============================================================================
// 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 easy2d

View File

@ -1,127 +0,0 @@
#pragma once
#include <easy2d/scene/sprite.h>
#include <easy2d/animation/animation_controller.h>
#include <easy2d/animation/animation_cache.h>
#include <string>
#include <array>
#include <vector>
#include <unordered_map>
#include <cstdint>
namespace easy2d {
// ============================================================================
// 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 easy2d

View File

@ -1,103 +0,0 @@
#pragma once
#include <easy2d/animation/animation_clip.h>
#include <unordered_map>
#include <mutex>
#include <string>
#include <functional>
namespace easy2d {
// 路径替换回调(对应原始 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() ::easy2d::AnimationCache::getInstance()
} // namespace easy2d

View File

@ -1,195 +0,0 @@
#pragma once
#include <easy2d/animation/animation_frame.h>
#include <easy2d/animation/sprite_frame_cache.h>
#include <vector>
#include <string>
#include <cassert>
namespace easy2d {
// ============================================================================
// 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 easy2d

View File

@ -1,106 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/animation/animation_clip.h>
#include <easy2d/animation/interpolation_engine.h>
#include <functional>
#include <string>
namespace easy2d {
// ============================================================================
// 动画播放状态
// ============================================================================
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 easy2d

View File

@ -1,62 +0,0 @@
#pragma once
#include <easy2d/animation/sprite_frame.h>
#include <easy2d/animation/frame_property.h>
#include <string>
#include <vector>
#include <array>
#include <cstdint>
namespace easy2d {
// ============================================================================
// 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 easy2d

View File

@ -1,107 +0,0 @@
#pragma once
#include <easy2d/scene/node.h>
#include <easy2d/animation/animation_clip.h>
#include <easy2d/animation/animation_controller.h>
#include <easy2d/animation/animation_cache.h>
#include <easy2d/animation/frame_renderer.h>
#include <easy2d/animation/animation_event.h>
#include <vector>
namespace easy2d {
// ============================================================================
// 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 easy2d

View File

@ -1,65 +0,0 @@
#pragma once
#include <easy2d/scene/node.h>
#include <easy2d/animation/animation_node.h>
#include <easy2d/animation/als_parser.h>
#include <easy2d/animation/animation_event.h>
#include <vector>
namespace easy2d {
// ============================================================================
// 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 easy2d

View File

@ -1,194 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/core/math_types.h>
#include <easy2d/core/color.h>
#include <variant>
#include <unordered_map>
#include <optional>
#include <any>
#include <string>
#include <vector>
namespace easy2d {
// ============================================================================
// 帧属性键 - 强类型枚举替代原始 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 多态值
// ============================================================================
using FramePropertyValue = std::variant<
bool,
int,
float,
std::string,
Vec2,
Color,
std::vector<int>
>;
// ============================================================================
// 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) {
properties_[key] = std::move(value);
}
void setCustom(const std::string& key, std::any value) {
customProperties_[key] = std::move(value);
}
// ------ 类型安全获取 ------
template<typename T>
std::optional<T> get(FramePropertyKey key) const {
auto it = properties_.find(key);
if (it == properties_.end()) return std::nullopt;
if (auto* val = std::get_if<T>(&it->second)) {
return *val;
}
return std::nullopt;
}
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 {
auto it = customProperties_.find(key);
if (it == customProperties_.end()) return std::nullopt;
return it->second;
}
// ------ 查询 ------
bool has(FramePropertyKey key) const {
return properties_.find(key) != properties_.end();
}
bool hasCustom(const std::string& key) const {
return customProperties_.find(key) != customProperties_.end();
}
bool empty() const {
return properties_.empty() && customProperties_.empty();
}
size_t count() const {
return properties_.size() + customProperties_.size();
}
// ------ 移除 ------
void remove(FramePropertyKey key) {
properties_.erase(key);
}
void removeCustom(const std::string& key) {
customProperties_.erase(key);
}
void clear() {
properties_.clear();
customProperties_.clear();
}
// ------ 迭代 ------
using PropertyMap = std::unordered_map<FramePropertyKey, FramePropertyValue, FramePropertyKeyHash>;
const PropertyMap& properties() const { return properties_; }
// ------ 链式 API ------
FramePropertySet& withSetFlag(int index) {
set(FramePropertyKey::SetFlag, index);
return *this;
}
FramePropertySet& withPlaySound(const std::string& path) {
set(FramePropertyKey::PlaySound, path);
return *this;
}
FramePropertySet& withImageRate(const Vec2& scale) {
set(FramePropertyKey::ImageRate, scale);
return *this;
}
FramePropertySet& withImageRotate(float degrees) {
set(FramePropertyKey::ImageRotate, degrees);
return *this;
}
FramePropertySet& withColorTint(const Color& color) {
set(FramePropertyKey::ColorTint, color);
return *this;
}
FramePropertySet& withInterpolation(bool enabled = true) {
set(FramePropertyKey::Interpolation, enabled);
return *this;
}
FramePropertySet& withBlendLinearDodge(bool enabled = true) {
set(FramePropertyKey::BlendLinearDodge, enabled);
return *this;
}
FramePropertySet& withLoop(bool enabled = true) {
set(FramePropertyKey::Loop, enabled);
return *this;
}
private:
PropertyMap properties_;
std::unordered_map<std::string, std::any> customProperties_;
};
} // namespace easy2d

View File

@ -1,69 +0,0 @@
#pragma once
#include <easy2d/animation/animation_frame.h>
#include <easy2d/animation/sprite_frame.h>
#include <easy2d/animation/sprite_frame_cache.h>
#include <easy2d/animation/interpolation_engine.h>
#include <easy2d/graphics/render_backend.h>
#include <vector>
namespace easy2d {
// ============================================================================
// 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 easy2d

View File

@ -1,105 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/core/math_types.h>
#include <easy2d/core/color.h>
#include <easy2d/animation/animation_frame.h>
#include <cmath>
namespace easy2d {
// ============================================================================
// 插值结果 - 两帧之间的插值后属性
// ============================================================================
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 easy2d

View File

@ -1,77 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/core/math_types.h>
#include <easy2d/graphics/texture.h>
#include <string>
namespace easy2d {
// ============================================================================
// 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 easy2d

View File

@ -1,169 +0,0 @@
#pragma once
#include <easy2d/animation/sprite_frame.h>
#include <easy2d/graphics/texture_pool.h>
#include <unordered_map>
#include <mutex>
#include <string>
namespace easy2d {
// ============================================================================
// 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 = TexturePool::getInstance().get(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;
}
// 缓存未命中,从 TexturePool 加载纹理并创建 SpriteFrame
auto texture = TexturePool::getInstance().get(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;
mutable std::mutex mutex_;
std::unordered_map<std::string, Ptr<SpriteFrame>> frames_;
};
// 便捷宏
#define E2D_SPRITE_FRAME_CACHE() ::easy2d::SpriteFrameCache::getInstance()
} // namespace easy2d

View File

@ -1,127 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/core/string.h>
#include <easy2d/graphics/render_backend.h>
#include <easy2d/platform/window.h>
#include <memory>
namespace easy2d {
// 前向声明
class Input;
class AudioEngine;
class SceneManager;
class ResourceManager;
class TimerManager;
class EventQueue;
class EventDispatcher;
class Camera;
// ============================================================================
// Application 配置
// ============================================================================
struct AppConfig {
String title = "Easy2D Application";
int width = 800;
int height = 600;
bool fullscreen = false;
bool resizable = true; // 窗口是否可调整大小
bool vsync = true;
int fpsLimit = 0; // 0 = 不限制
BackendType renderBackend = BackendType::OpenGL;
int msaaSamples = 0;
};
// ============================================================================
// Application 单例 - 应用主控
// ============================================================================
class Application {
public:
// Meyer's 单例
static Application& instance();
// 禁止拷贝
Application(const Application&) = delete;
Application& operator=(const Application&) = delete;
// ------------------------------------------------------------------------
// 生命周期
// ------------------------------------------------------------------------
bool init(const AppConfig& config);
void shutdown();
void run();
void quit();
// ------------------------------------------------------------------------
// 状态控制
// ------------------------------------------------------------------------
void pause();
void resume();
bool isPaused() const { return paused_; }
bool isRunning() const { return running_; }
// ------------------------------------------------------------------------
// 子系统访问
// ------------------------------------------------------------------------
Window& window() { return *window_; }
RenderBackend& renderer() { return *renderer_; }
Input& input();
AudioEngine& audio();
SceneManager& scenes();
ResourceManager& resources();
TimerManager& timers();
EventQueue& eventQueue();
EventDispatcher& eventDispatcher();
Camera& camera();
// ------------------------------------------------------------------------
// 便捷方法
// ------------------------------------------------------------------------
void enterScene(Ptr<class Scene> scene);
void enterScene(Ptr<class Scene> scene, Ptr<class Transition> transition);
float deltaTime() const { return deltaTime_; }
float totalTime() const { return totalTime_; }
int fps() const { return currentFps_; }
// 获取配置
const AppConfig& getConfig() const { return config_; }
private:
Application() = default;
~Application();
void mainLoop();
void update();
void render();
// 配置
AppConfig config_;
// 子系统
UniquePtr<Window> window_;
UniquePtr<RenderBackend> renderer_;
UniquePtr<SceneManager> sceneManager_;
UniquePtr<ResourceManager> resourceManager_;
UniquePtr<TimerManager> timerManager_;
UniquePtr<EventQueue> eventQueue_;
UniquePtr<EventDispatcher> eventDispatcher_;
UniquePtr<Camera> camera_;
// 状态
bool initialized_ = false;
bool running_ = false;
bool paused_ = false;
bool shouldQuit_ = false;
// 时间
float deltaTime_ = 0.0f;
float totalTime_ = 0.0f;
double lastFrameTime_ = 0.0;
int frameCount_ = 0;
float fpsTimer_ = 0.0f;
int currentFps_ = 0;
};
} // namespace easy2d

View File

@ -1,131 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <glm/vec4.hpp>
#include <algorithm>
namespace easy2d {
/// RGBA 颜色(浮点数,每通道 0.0 - 1.0
struct Color {
float r = 0.0f;
float g = 0.0f;
float b = 0.0f;
float a = 1.0f;
constexpr Color() = default;
constexpr Color(float r, float g, float b, float a = 1.0f)
: r(r), g(g), b(b), a(a) {}
/// 从 0xRRGGBB 整数构造
constexpr explicit Color(uint32_t rgb, float a = 1.0f)
: r(static_cast<float>((rgb >> 16) & 0xFF) / 255.0f)
, g(static_cast<float>((rgb >> 8) & 0xFF) / 255.0f)
, b(static_cast<float>((rgb) & 0xFF) / 255.0f)
, a(a) {}
/// 从 0-255 整数构造
static constexpr Color fromRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) {
return Color(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
}
/// 转换为 glm::vec4
glm::vec4 toVec4() const { return {r, g, b, a}; }
/// 线性插值
static Color lerp(const Color& a, const Color& b, float t) {
t = std::clamp(t, 0.0f, 1.0f);
return Color(
a.r + (b.r - a.r) * t,
a.g + (b.g - a.g) * t,
a.b + (b.b - a.b) * t,
a.a + (b.a - a.a) * t
);
}
bool operator==(const Color& other) const {
return r == other.r && g == other.g && b == other.b && a == other.a;
}
bool operator!=(const Color& other) const {
return !(*this == other);
}
// 算术运算符
Color operator+(const Color& other) const {
return Color(r + other.r, g + other.g, b + other.b, a + other.a);
}
Color operator-(const Color& other) const {
return Color(r - other.r, g - other.g, b - other.b, a - other.a);
}
Color operator*(float scalar) const {
return Color(r * scalar, g * scalar, b * scalar, a * scalar);
}
Color operator/(float scalar) const {
return Color(r / scalar, g / scalar, b / scalar, a / scalar);
}
Color& operator+=(const Color& other) {
r += other.r; g += other.g; b += other.b; a += other.a;
return *this;
}
Color& operator-=(const Color& other) {
r -= other.r; g -= other.g; b -= other.b; a -= other.a;
return *this;
}
Color& operator*=(float scalar) {
r *= scalar; g *= scalar; b *= scalar; a *= scalar;
return *this;
}
Color& operator/=(float scalar) {
r /= scalar; g /= scalar; b /= scalar; a /= scalar;
return *this;
}
};
// 命名颜色常量
namespace Colors {
inline constexpr Color White {1.0f, 1.0f, 1.0f, 1.0f};
inline constexpr Color Black {0.0f, 0.0f, 0.0f, 1.0f};
inline constexpr Color Red {1.0f, 0.0f, 0.0f, 1.0f};
inline constexpr Color Green {0.0f, 1.0f, 0.0f, 1.0f};
inline constexpr Color Blue {0.0f, 0.0f, 1.0f, 1.0f};
inline constexpr Color Yellow {1.0f, 1.0f, 0.0f, 1.0f};
inline constexpr Color Cyan {0.0f, 1.0f, 1.0f, 1.0f};
inline constexpr Color Magenta {1.0f, 0.0f, 1.0f, 1.0f};
inline constexpr Color Orange {1.0f, 0.647f, 0.0f, 1.0f};
inline constexpr Color Purple {0.502f, 0.0f, 0.502f, 1.0f};
inline constexpr Color Pink {1.0f, 0.753f, 0.796f, 1.0f};
inline constexpr Color Gray {0.502f, 0.502f, 0.502f, 1.0f};
inline constexpr Color LightGray {0.827f, 0.827f, 0.827f, 1.0f};
inline constexpr Color DarkGray {0.412f, 0.412f, 0.412f, 1.0f};
inline constexpr Color Brown {0.647f, 0.165f, 0.165f, 1.0f};
inline constexpr Color Gold {1.0f, 0.843f, 0.0f, 1.0f};
inline constexpr Color Silver {0.753f, 0.753f, 0.753f, 1.0f};
inline constexpr Color SkyBlue {0.529f, 0.808f, 0.922f, 1.0f};
inline constexpr Color LimeGreen {0.196f, 0.804f, 0.196f, 1.0f};
inline constexpr Color Coral {1.0f, 0.498f, 0.314f, 1.0f};
inline constexpr Color Transparent {0.0f, 0.0f, 0.0f, 0.0f};
}
// 为了向后兼容,在 Color 结构体内提供静态引用
struct ColorConstants {
static const Color& White;
static const Color& Black;
static const Color& Red;
static const Color& Green;
static const Color& Blue;
static const Color& Yellow;
static const Color& Cyan;
static const Color& Magenta;
static const Color& Transparent;
};
} // namespace easy2d

View File

@ -1,295 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <glm/vec2.hpp>
#include <glm/mat4x4.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <cmath>
#include <algorithm>
namespace easy2d {
// ---------------------------------------------------------------------------
// 常量
// ---------------------------------------------------------------------------
constexpr float PI_F = 3.14159265358979323846f;
constexpr float DEG_TO_RAD = PI_F / 180.0f;
constexpr float RAD_TO_DEG = 180.0f / PI_F;
// ---------------------------------------------------------------------------
// 2D 向量
// ---------------------------------------------------------------------------
struct Vec2 {
float x = 0.0f;
float y = 0.0f;
constexpr Vec2() = default;
constexpr Vec2(float x, float y) : x(x), y(y) {}
explicit Vec2(const glm::vec2& v) : x(v.x), y(v.y) {}
glm::vec2 toGlm() const { return {x, y}; }
static Vec2 fromGlm(const glm::vec2& v) { return {v.x, v.y}; }
// 基础运算
Vec2 operator+(const Vec2& v) const { return {x + v.x, y + v.y}; }
Vec2 operator-(const Vec2& v) const { return {x - v.x, y - v.y}; }
Vec2 operator*(float s) const { return {x * s, y * s}; }
Vec2 operator/(float s) const { return {x / s, y / s}; }
Vec2 operator-() const { return {-x, -y}; }
Vec2& operator+=(const Vec2& v) { x += v.x; y += v.y; return *this; }
Vec2& operator-=(const Vec2& v) { x -= v.x; y -= v.y; return *this; }
Vec2& operator*=(float s) { x *= s; y *= s; return *this; }
Vec2& operator/=(float s) { x /= s; y /= s; return *this; }
bool operator==(const Vec2& v) const { return x == v.x && y == v.y; }
bool operator!=(const Vec2& v) const { return !(*this == v); }
// 向量运算
float length() const { return std::sqrt(x * x + y * y); }
float lengthSquared() const { return x * x + y * y; }
Vec2 normalized() const {
float len = length();
if (len > 0.0f) return {x / len, y / len};
return {0.0f, 0.0f};
}
float dot(const Vec2& v) const { return x * v.x + y * v.y; }
float cross(const Vec2& v) const { return x * v.y - y * v.x; }
float distance(const Vec2& v) const { return (*this - v).length(); }
float angle() const { return std::atan2(y, x) * RAD_TO_DEG; }
static Vec2 lerp(const Vec2& a, const Vec2& b, float t) {
return a + (b - a) * t;
}
static constexpr Vec2 Zero() { return {0.0f, 0.0f}; }
static constexpr Vec2 One() { return {1.0f, 1.0f}; }
static constexpr Vec2 UnitX() { return {1.0f, 0.0f}; }
static constexpr Vec2 UnitY() { return {0.0f, 1.0f}; }
};
inline Vec2 operator*(float s, const Vec2& v) { return v * s; }
using Point = Vec2;
// ---------------------------------------------------------------------------
// 3D 向量 (用于3D动作)
// ---------------------------------------------------------------------------
struct Vec3 {
float x = 0.0f;
float y = 0.0f;
float z = 0.0f;
constexpr Vec3() = default;
constexpr Vec3(float x, float y, float z) : x(x), y(y), z(z) {}
explicit Vec3(const glm::vec3& v) : x(v.x), y(v.y), z(v.z) {}
glm::vec3 toGlm() const { return {x, y, z}; }
static Vec3 fromGlm(const glm::vec3& v) { return {v.x, v.y, v.z}; }
Vec3 operator+(const Vec3& v) const { return {x + v.x, y + v.y, z + v.z}; }
Vec3 operator-(const Vec3& v) const { return {x - v.x, y - v.y, z - v.z}; }
Vec3 operator*(float s) const { return {x * s, y * s, z * s}; }
Vec3 operator/(float s) const { return {x / s, y / s, z / s}; }
Vec3 operator-() const { return {-x, -y, -z}; }
Vec3& operator+=(const Vec3& v) { x += v.x; y += v.y; z += v.z; return *this; }
Vec3& operator-=(const Vec3& v) { x -= v.x; y -= v.y; z -= v.z; return *this; }
Vec3& operator*=(float s) { x *= s; y *= s; z *= s; return *this; }
Vec3& operator/=(float s) { x /= s; y /= s; z /= s; return *this; }
bool operator==(const Vec3& v) const { return x == v.x && y == v.y && z == v.z; }
bool operator!=(const Vec3& v) const { return !(*this == v); }
float length() const { return std::sqrt(x * x + y * y + z * z); }
float lengthSquared() const { return x * x + y * y + z * z; }
Vec3 normalized() const {
float len = length();
if (len > 0.0f) return {x / len, y / len, z / len};
return {0.0f, 0.0f, 0.0f};
}
float dot(const Vec3& v) const { return x * v.x + y * v.y + z * v.z; }
static Vec3 lerp(const Vec3& a, const Vec3& b, float t) {
return a + (b - a) * t;
}
static constexpr Vec3 Zero() { return {0.0f, 0.0f, 0.0f}; }
static constexpr Vec3 One() { return {1.0f, 1.0f, 1.0f}; }
};
inline Vec3 operator*(float s, const Vec3& v) { return v * s; }
// ---------------------------------------------------------------------------
// 2D 尺寸
// ---------------------------------------------------------------------------
struct Size {
float width = 0.0f;
float height = 0.0f;
constexpr Size() = default;
constexpr Size(float w, float h) : width(w), height(h) {}
bool operator==(const Size& s) const { return width == s.width && height == s.height; }
bool operator!=(const Size& s) const { return !(*this == s); }
float area() const { return width * height; }
bool empty() const { return width <= 0.0f || height <= 0.0f; }
static constexpr Size Zero() { return {0.0f, 0.0f}; }
};
// ---------------------------------------------------------------------------
// 2D 矩形
// ---------------------------------------------------------------------------
struct Rect {
Point origin;
Size size;
constexpr Rect() = default;
constexpr Rect(float x, float y, float w, float h) : origin(x, y), size(w, h) {}
constexpr Rect(const Point& o, const Size& s) : origin(o), size(s) {}
float left() const { return origin.x; }
float top() const { return origin.y; }
float right() const { return origin.x + size.width; }
float bottom() const { return origin.y + size.height; }
float width() const { return size.width; }
float height() const { return size.height; }
Point center() const { return {origin.x + size.width * 0.5f, origin.y + size.height * 0.5f}; }
bool empty() const { return size.empty(); }
bool containsPoint(const Point& p) const {
return p.x >= left() && p.x <= right() &&
p.y >= top() && p.y <= bottom();
}
bool contains(const Rect& r) const {
return r.left() >= left() && r.right() <= right() &&
r.top() >= top() && r.bottom() <= bottom();
}
bool intersects(const Rect& r) const {
return !(left() > r.right() || right() < r.left() ||
top() > r.bottom() || bottom() < r.top());
}
Rect intersection(const Rect& r) const {
float l = std::max(left(), r.left());
float t = std::max(top(), r.top());
float ri = std::min(right(), r.right());
float b = std::min(bottom(), r.bottom());
if (l < ri && t < b) return {l, t, ri - l, b - t};
return {};
}
Rect unionWith(const Rect& r) const {
if (empty()) return r;
if (r.empty()) return *this;
float l = std::min(left(), r.left());
float t = std::min(top(), r.top());
float ri = std::max(right(), r.right());
float b = std::max(bottom(), r.bottom());
return {l, t, ri - l, b - t};
}
bool operator==(const Rect& r) const { return origin == r.origin && size == r.size; }
bool operator!=(const Rect& r) const { return !(*this == r); }
static constexpr Rect Zero() { return {0, 0, 0, 0}; }
};
// ---------------------------------------------------------------------------
// 2D 变换矩阵(基于 glm::mat4兼容 OpenGL
// ---------------------------------------------------------------------------
struct Transform2D {
glm::mat4 matrix{1.0f}; // 单位矩阵
Transform2D() = default;
explicit Transform2D(const glm::mat4& m) : matrix(m) {}
static Transform2D identity() { return Transform2D{}; }
static Transform2D translation(float x, float y) {
Transform2D t;
t.matrix = glm::translate(glm::mat4(1.0f), glm::vec3(x, y, 0.0f));
return t;
}
static Transform2D translation(const Vec2& v) {
return translation(v.x, v.y);
}
static Transform2D rotation(float degrees) {
Transform2D t;
t.matrix = glm::rotate(glm::mat4(1.0f), degrees * DEG_TO_RAD, glm::vec3(0.0f, 0.0f, 1.0f));
return t;
}
static Transform2D scaling(float sx, float sy) {
Transform2D t;
t.matrix = glm::scale(glm::mat4(1.0f), glm::vec3(sx, sy, 1.0f));
return t;
}
static Transform2D scaling(float s) {
return scaling(s, s);
}
static Transform2D skewing(float skewX, float skewY) {
Transform2D t;
t.matrix = glm::mat4(1.0f);
t.matrix[1][0] = std::tan(skewX * DEG_TO_RAD);
t.matrix[0][1] = std::tan(skewY * DEG_TO_RAD);
return t;
}
Transform2D operator*(const Transform2D& other) const {
return Transform2D(matrix * other.matrix);
}
Transform2D& operator*=(const Transform2D& other) {
matrix *= other.matrix;
return *this;
}
Vec2 transformPoint(const Vec2& p) const {
glm::vec4 result = matrix * glm::vec4(p.x, p.y, 0.0f, 1.0f);
return {result.x, result.y};
}
Transform2D inverse() const {
return Transform2D(glm::inverse(matrix));
}
};
// ---------------------------------------------------------------------------
// 数学工具函数
// ---------------------------------------------------------------------------
namespace math {
inline float clamp(float value, float minVal, float maxVal) {
return std::clamp(value, minVal, maxVal);
}
inline float lerp(float a, float b, float t) {
return a + (b - a) * t;
}
inline float degrees(float radians) {
return radians * RAD_TO_DEG;
}
inline float radians(float degrees) {
return degrees * DEG_TO_RAD;
}
} // namespace math
} // namespace easy2d

View File

@ -1,97 +0,0 @@
#pragma once
// Easy2D v3.0 - 统一入口头文件
// 包含所有公共 API
// Core
#include <easy2d/core/types.h>
#include <easy2d/core/string.h>
#include <easy2d/core/color.h>
#include <easy2d/core/math_types.h>
// Platform
#include <easy2d/platform/window.h>
#include <easy2d/platform/input.h>
// Graphics
#include <easy2d/graphics/render_backend.h>
#include <easy2d/graphics/texture.h>
#include <easy2d/graphics/font.h>
#include <easy2d/graphics/camera.h>
#include <easy2d/graphics/shader_system.h>
#include <easy2d/graphics/texture_pool.h>
#include <easy2d/graphics/render_target.h>
#include <easy2d/graphics/vram_manager.h>
// Scene
#include <easy2d/scene/node.h>
#include <easy2d/scene/scene.h>
#include <easy2d/scene/sprite.h>
#include <easy2d/scene/text.h>
#include <easy2d/scene/shape_node.h>
#include <easy2d/scene/scene_manager.h>
#include <easy2d/scene/transition.h>
// Animation
#include <easy2d/animation/sprite_frame.h>
#include <easy2d/animation/sprite_frame_cache.h>
#include <easy2d/animation/frame_property.h>
#include <easy2d/animation/animation_frame.h>
#include <easy2d/animation/animation_clip.h>
#include <easy2d/animation/animation_controller.h>
#include <easy2d/animation/animation_cache.h>
#include <easy2d/animation/interpolation_engine.h>
#include <easy2d/animation/animated_sprite.h>
#include <easy2d/animation/frame_renderer.h>
#include <easy2d/animation/animation_event.h>
#include <easy2d/animation/animation_node.h>
#include <easy2d/animation/composite_animation.h>
#include <easy2d/animation/ani_parser.h>
#include <easy2d/animation/ani_binary_parser.h>
#include <easy2d/animation/als_parser.h>
// UI
#include <easy2d/ui/widget.h>
#include <easy2d/ui/button.h>
// Action
#include <easy2d/action/action.h>
#include <easy2d/action/actions.h>
#include <easy2d/action/ease.h>
// Event
#include <easy2d/event/event.h>
#include <easy2d/event/event_queue.h>
#include <easy2d/event/event_dispatcher.h>
#include <easy2d/event/input_codes.h>
// Audio
#include <easy2d/audio/audio_engine.h>
#include <easy2d/audio/sound.h>
// Resource
#include <easy2d/resource/resource_manager.h>
// Utils
#include <easy2d/utils/logger.h>
#include <easy2d/utils/timer.h>
#include <easy2d/utils/data.h>
#include <easy2d/utils/random.h>
// Spatial
#include <easy2d/spatial/spatial_index.h>
#include <easy2d/spatial/quadtree.h>
#include <easy2d/spatial/spatial_hash.h>
#include <easy2d/spatial/spatial_manager.h>
// Effects
#include <easy2d/effects/post_process.h>
#include <easy2d/effects/particle_system.h>
#include <easy2d/effects/custom_effect_manager.h>
// Application
#include <easy2d/app/application.h>
// Script
#include <easy2d/script/script_engine.h>
#include <easy2d/script/script_node.h>

View File

@ -1,318 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/core/color.h>
#include <easy2d/effects/particle_system.h>
#include <easy2d/effects/post_process.h>
#include <easy2d/graphics/shader_system.h>
#include <string>
#include <unordered_map>
#include <functional>
#include <vector>
namespace easy2d {
// ============================================================================
// 自定义特效类型
// ============================================================================
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() ::easy2d::CustomEffectManager::getInstance()
#define E2D_CUSTOM_EFFECT_FACTORY() ::easy2d::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 easy2d

View File

@ -1,244 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/core/color.h>
#include <easy2d/scene/node.h>
#include <easy2d/graphics/texture.h>
#include <vector>
#include <functional>
#include <random>
namespace easy2d {
// ============================================================================
// 粒子数据
// ============================================================================
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;
// ------------------------------------------------------------------------
// 初始化和关闭
// ------------------------------------------------------------------------
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;
std::mt19937 rng_;
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 easy2d

View File

@ -1,225 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/core/color.h>
#include <easy2d/graphics/texture.h>
#include <easy2d/graphics/opengl/gl_shader.h>
#include <string>
#include <functional>
#include <vector>
namespace easy2d {
// ============================================================================
// 前向声明
// ============================================================================
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() ::easy2d::PostProcessManager::getInstance()
} // namespace easy2d

View File

@ -1,185 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/core/math_types.h>
#include <cstdint>
#include <variant>
namespace easy2d {
// ============================================================================
// 事件类型枚举
// ============================================================================
enum class EventType {
None = 0,
// 窗口事件
WindowClose,
WindowResize,
WindowFocus,
WindowLostFocus,
WindowMoved,
// 键盘事件
KeyPressed,
KeyReleased,
KeyRepeat,
// 鼠标事件
MouseButtonPressed,
MouseButtonReleased,
MouseMoved,
MouseScrolled,
// UI 事件
UIHoverEnter,
UIHoverExit,
UIPressed,
UIReleased,
UIClicked,
// 游戏手柄事件
GamepadConnected,
GamepadDisconnected,
GamepadButtonPressed,
GamepadButtonReleased,
GamepadAxisMoved,
// 触摸事件 (移动端)
TouchBegan,
TouchMoved,
TouchEnded,
TouchCancelled,
// 自定义事件
Custom
};
// ============================================================================
// 键盘事件数据
// ============================================================================
struct KeyEvent {
int keyCode;
int scancode;
int mods; // 修饰键 (Shift, Ctrl, Alt, etc.)
};
// ============================================================================
// 鼠标事件数据
// ============================================================================
struct MouseButtonEvent {
int button;
int mods;
Vec2 position;
};
struct MouseMoveEvent {
Vec2 position;
Vec2 delta;
};
struct MouseScrollEvent {
Vec2 offset;
Vec2 position;
};
// ============================================================================
// 窗口事件数据
// ============================================================================
struct WindowResizeEvent {
int width;
int height;
};
struct WindowMoveEvent {
int x;
int y;
};
// ============================================================================
// 游戏手柄事件数据
// ============================================================================
struct GamepadButtonEvent {
int gamepadId;
int button;
};
struct GamepadAxisEvent {
int gamepadId;
int axis;
float value;
};
// ============================================================================
// 触摸事件数据
// ============================================================================
struct TouchEvent {
int touchId;
Vec2 position;
};
// ============================================================================
// 自定义事件数据
// ============================================================================
struct CustomEvent {
uint32_t id;
void* data;
};
// ============================================================================
// 事件结构
// ============================================================================
struct Event {
EventType type = EventType::None;
double timestamp = 0.0;
bool handled = false;
// 事件数据联合体
std::variant<
std::monostate,
KeyEvent,
MouseButtonEvent,
MouseMoveEvent,
MouseScrollEvent,
WindowResizeEvent,
WindowMoveEvent,
GamepadButtonEvent,
GamepadAxisEvent,
TouchEvent,
CustomEvent
> data;
// 便捷访问方法
bool isWindowEvent() const {
return type == EventType::WindowClose ||
type == EventType::WindowResize ||
type == EventType::WindowFocus ||
type == EventType::WindowLostFocus ||
type == EventType::WindowMoved;
}
bool isKeyboardEvent() const {
return type == EventType::KeyPressed ||
type == EventType::KeyReleased ||
type == EventType::KeyRepeat;
}
bool isMouseEvent() const {
return type == EventType::MouseButtonPressed ||
type == EventType::MouseButtonReleased ||
type == EventType::MouseMoved ||
type == EventType::MouseScrolled;
}
// 静态工厂方法
static Event createWindowResize(int width, int height);
static Event createWindowClose();
static Event createKeyPress(int keyCode, int scancode, int mods);
static Event createKeyRelease(int keyCode, int scancode, int mods);
static Event createMouseButtonPress(int button, int mods, const Vec2& pos);
static Event createMouseButtonRelease(int button, int mods, const Vec2& pos);
static Event createMouseMove(const Vec2& pos, const Vec2& delta);
static Event createMouseScroll(const Vec2& offset, const Vec2& pos);
};
} // namespace easy2d

View File

@ -1,56 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/event/event.h>
#include <functional>
#include <unordered_map>
#include <vector>
namespace easy2d {
// ============================================================================
// 事件监听器 ID
// ============================================================================
using ListenerId = uint64_t;
// ============================================================================
// 事件分发器
// ============================================================================
class EventDispatcher {
public:
using EventCallback = std::function<void(Event&)>;
EventDispatcher();
~EventDispatcher() = default;
// 添加监听器
ListenerId addListener(EventType type, EventCallback callback);
// 移除监听器
void removeListener(ListenerId id);
void removeAllListeners(EventType type);
void removeAllListeners();
// 分发事件
void dispatch(Event& event);
void dispatch(const Event& event);
// 处理事件队列
void processQueue(class EventQueue& queue);
// 统计
size_t getListenerCount(EventType type) const;
size_t getTotalListenerCount() const;
private:
struct Listener {
ListenerId id;
EventType type;
EventCallback callback;
};
std::unordered_map<EventType, std::vector<Listener>> listeners_;
ListenerId nextId_;
};
} // namespace easy2d

View File

@ -1,40 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/event/event.h>
#include <queue>
#include <mutex>
namespace easy2d {
// ============================================================================
// 事件队列 - 线程安全的事件队列
// ============================================================================
class EventQueue {
public:
EventQueue();
~EventQueue() = default;
// 添加事件到队列
void push(const Event& event);
void push(Event&& event);
// 从队列取出事件
bool poll(Event& event);
// 查看队列头部事件(不移除)
bool peek(Event& event) const;
// 清空队列
void clear();
// 队列状态
bool empty() const;
size_t size() const;
private:
std::queue<Event> queue_;
mutable std::mutex mutex_;
};
} // namespace easy2d

View File

@ -1,155 +0,0 @@
#pragma once
namespace easy2d {
// ============================================================================
// 键盘按键码 (基于 GLFW)
// ============================================================================
namespace Key {
enum : int {
Unknown = -1,
Space = 32,
Apostrophe = 39,
Comma = 44,
Minus = 45,
Period = 46,
Slash = 47,
Num0 = 48, Num1 = 49, Num2 = 50, Num3 = 51, Num4 = 52,
Num5 = 53, Num6 = 54, Num7 = 55, Num8 = 56, Num9 = 57,
Semicolon = 59,
Equal = 61,
A = 65, B = 66, C = 67, D = 68, E = 69, F = 70, G = 71, H = 72,
I = 73, J = 74, K = 75, L = 76, M = 77, N = 78, O = 79, P = 80,
Q = 81, R = 82, S = 83, T = 84, U = 85, V = 86, W = 87, X = 88,
Y = 89, Z = 90,
LeftBracket = 91,
Backslash = 92,
RightBracket = 93,
GraveAccent = 96,
World1 = 161,
World2 = 162,
Escape = 256,
Enter = 257,
Tab = 258,
Backspace = 259,
Insert = 260,
Delete = 261,
Right = 262,
Left = 263,
Down = 264,
Up = 265,
PageUp = 266,
PageDown = 267,
Home = 268,
End = 269,
CapsLock = 280,
ScrollLock = 281,
NumLock = 282,
PrintScreen = 283,
Pause = 284,
F1 = 290, F2 = 291, F3 = 292, F4 = 293, F5 = 294, F6 = 295,
F7 = 296, F8 = 297, F9 = 298, F10 = 299, F11 = 300, F12 = 301,
F13 = 302, F14 = 303, F15 = 304, F16 = 305, F17 = 306, F18 = 307,
F19 = 308, F20 = 309, F21 = 310, F22 = 311, F23 = 312, F24 = 313,
F25 = 314,
KP0 = 320, KP1 = 321, KP2 = 322, KP3 = 323, KP4 = 324,
KP5 = 325, KP6 = 326, KP7 = 327, KP8 = 328, KP9 = 329,
KPDecimal = 330,
KPDivide = 331,
KPMultiply = 332,
KPSubtract = 333,
KPAdd = 334,
KPEnter = 335,
KPEqual = 336,
LeftShift = 340,
LeftControl = 341,
LeftAlt = 342,
LeftSuper = 343,
RightShift = 344,
RightControl = 345,
RightAlt = 346,
RightSuper = 347,
Menu = 348,
Last = Menu
};
}
// ============================================================================
// 修饰键
// ============================================================================
namespace Mod {
enum : int {
Shift = 0x0001,
Control = 0x0002,
Alt = 0x0004,
Super = 0x0008,
CapsLock = 0x0010,
NumLock = 0x0020
};
}
// ============================================================================
// 鼠标按键码
// ============================================================================
namespace Mouse {
enum : int {
Button1 = 0,
Button2 = 1,
Button3 = 2,
Button4 = 3,
Button5 = 4,
Button6 = 5,
Button7 = 6,
Button8 = 7,
ButtonLast = Button8,
ButtonLeft = Button1,
ButtonRight = Button2,
ButtonMiddle = Button3
};
}
// ============================================================================
// 游戏手柄按键
// ============================================================================
namespace GamepadButton {
enum : int {
A = 0,
B = 1,
X = 2,
Y = 3,
LeftBumper = 4,
RightBumper = 5,
Back = 6,
Start = 7,
Guide = 8,
LeftThumb = 9,
RightThumb = 10,
DPadUp = 11,
DPadRight = 12,
DPadDown = 13,
DPadLeft = 14,
Last = DPadLeft,
Cross = A,
Circle = B,
Square = X,
Triangle = Y
};
}
// ============================================================================
// 游戏手柄轴
// ============================================================================
namespace GamepadAxis {
enum : int {
LeftX = 0,
LeftY = 1,
RightX = 2,
RightY = 3,
LeftTrigger = 4,
RightTrigger = 5,
Last = RightTrigger
};
}
} // namespace easy2d

View File

@ -1,46 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/core/math_types.h>
#include <vector>
namespace easy2d {
// ============================================================================
// Alpha 遮罩 - 存储图片的非透明区域信息
// ============================================================================
class AlphaMask {
public:
AlphaMask() = default;
AlphaMask(int width, int height);
/// 从像素数据创建遮罩
static AlphaMask createFromPixels(const uint8_t* pixels, int width, int height, int channels);
/// 获取指定位置的透明度0-255
uint8_t getAlpha(int x, int y) const;
/// 检查指定位置是否不透明
bool isOpaque(int x, int y, uint8_t threshold = 128) const;
/// 检查指定位置是否在遮罩范围内
bool isValid(int x, int y) const;
/// 获取遮罩尺寸
int getWidth() const { return width_; }
int getHeight() const { return height_; }
Size getSize() const { return Size(static_cast<float>(width_), static_cast<float>(height_)); }
/// 获取原始数据
const std::vector<uint8_t>& getData() const { return data_; }
/// 检查遮罩是否有效
bool isValid() const { return !data_.empty() && width_ > 0 && height_ > 0; }
private:
int width_ = 0;
int height_ = 0;
std::vector<uint8_t> data_; // Alpha值数组
};
} // namespace easy2d

View File

@ -1,93 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/core/math_types.h>
#include <easy2d/core/color.h>
#include <glm/mat4x4.hpp>
namespace easy2d {
// ============================================================================
// 2D 正交相机
// ============================================================================
class Camera {
public:
Camera();
Camera(float left, float right, float bottom, float top);
Camera(const Size& viewport);
~Camera() = default;
// ------------------------------------------------------------------------
// 位置和变换
// ------------------------------------------------------------------------
void setPosition(const Vec2& position);
void setPosition(float x, float y);
Vec2 getPosition() const { return position_; }
void setRotation(float degrees);
float getRotation() const { return rotation_; }
void setZoom(float zoom);
float getZoom() const { return zoom_; }
// ------------------------------------------------------------------------
// 视口设置
// ------------------------------------------------------------------------
void setViewport(float left, float right, float bottom, float top);
void setViewport(const Rect& rect);
Rect getViewport() const;
// ------------------------------------------------------------------------
// 矩阵获取
// ------------------------------------------------------------------------
glm::mat4 getViewMatrix() const;
glm::mat4 getProjectionMatrix() const;
glm::mat4 getViewProjectionMatrix() const;
// ------------------------------------------------------------------------
// 坐标转换
// ------------------------------------------------------------------------
Vec2 screenToWorld(const Vec2& screenPos) const;
Vec2 worldToScreen(const Vec2& worldPos) const;
Vec2 screenToWorld(float x, float y) const;
Vec2 worldToScreen(float x, float y) const;
// ------------------------------------------------------------------------
// 移动相机
// ------------------------------------------------------------------------
void move(const Vec2& offset);
void move(float x, float y);
// ------------------------------------------------------------------------
// 边界限制
// ------------------------------------------------------------------------
void setBounds(const Rect& bounds);
void clearBounds();
void clampToBounds();
// ------------------------------------------------------------------------
// 快捷方法:看向某点
// ------------------------------------------------------------------------
void lookAt(const Vec2& target);
private:
Vec2 position_ = Vec2::Zero();
float rotation_ = 0.0f;
float zoom_ = 1.0f;
float left_ = -1.0f;
float right_ = 1.0f;
float bottom_ = -1.0f;
float top_ = 1.0f;
Rect bounds_;
bool hasBounds_ = false;
mutable glm::mat4 viewMatrix_;
mutable glm::mat4 projMatrix_;
mutable glm::mat4 vpMatrix_;
mutable bool viewDirty_ = true;
mutable bool projDirty_ = true;
};
} // namespace easy2d

View File

@ -1,51 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/core/color.h>
#include <easy2d/core/string.h>
#include <easy2d/core/math_types.h>
namespace easy2d {
// ============================================================================
// 字形信息
// ============================================================================
struct Glyph {
float u0, v0; // 纹理坐标左下角
float u1, v1; // 纹理坐标右上角
float width; // 字形宽度(像素)
float height; // 字形高度(像素)
float bearingX; // 水平偏移
float bearingY; // 垂直偏移
float advance; // 前进距离
};
// ============================================================================
// 字体图集接口
// ============================================================================
class FontAtlas {
public:
virtual ~FontAtlas() = default;
// 获取字形信息
virtual const Glyph* getGlyph(char32_t codepoint) const = 0;
// 获取纹理
virtual class Texture* getTexture() const = 0;
// 获取字体大小
virtual int getFontSize() const = 0;
virtual float getAscent() const = 0;
virtual float getDescent() const = 0;
virtual float getLineGap() const = 0;
virtual float getLineHeight() const = 0;
// 计算文字尺寸
virtual Vec2 measureText(const String& text) = 0;
// 是否支持 SDF 渲染
virtual bool isSDF() const = 0;
};
} // namespace easy2d

View File

@ -1,63 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/core/color.h>
#include <easy2d/core/math_types.h>
#include <easy2d/graphics/font.h>
#include <easy2d/graphics/texture.h>
#include <easy2d/graphics/opengl/gl_texture.h>
#include <stb/stb_truetype.h>
#include <stb/stb_rect_pack.h>
#include <unordered_map>
#include <vector>
#include <memory>
namespace easy2d {
// ============================================================================
// OpenGL 字体图集实现 - 使用 stb_rect_pack 进行矩形打包
// ============================================================================
class GLFontAtlas : public FontAtlas {
public:
GLFontAtlas(const std::string& filepath, int fontSize, bool useSDF = false);
~GLFontAtlas();
// FontAtlas 接口实现
const Glyph* getGlyph(char32_t codepoint) const override;
Texture* getTexture() const override { return texture_.get(); }
int getFontSize() const override { return fontSize_; }
float getAscent() const override { return ascent_; }
float getDescent() const override { return descent_; }
float getLineGap() const override { return lineGap_; }
float getLineHeight() const override { return ascent_ - descent_ + lineGap_; }
Vec2 measureText(const String& text) override;
bool isSDF() const override { return useSDF_; }
private:
// 图集配置
static constexpr int ATLAS_WIDTH = 512;
static constexpr int ATLAS_HEIGHT = 512;
static constexpr int PADDING = 2; // 字形之间的间距
int fontSize_;
bool useSDF_;
mutable std::unique_ptr<GLTexture> texture_;
mutable std::unordered_map<char32_t, Glyph> glyphs_;
// stb_rect_pack 上下文
mutable stbrp_context packContext_;
mutable std::vector<stbrp_node> packNodes_;
mutable int currentY_;
std::vector<unsigned char> fontData_;
stbtt_fontinfo fontInfo_;
float scale_;
float ascent_;
float descent_;
float lineGap_;
void createAtlas();
void cacheGlyph(char32_t codepoint) const;
};
} // namespace easy2d

View File

@ -1,76 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/core/color.h>
#include <easy2d/core/math_types.h>
#include <easy2d/graphics/texture.h>
#include <easy2d/graphics/opengl/gl_shader.h>
#include <glm/mat4x4.hpp>
#include <vector>
// 使用标准 GLES3.2
#include <GLES3/gl32.h>
namespace easy2d {
// ============================================================================
// OpenGL 精灵批渲染器
// ============================================================================
class GLSpriteBatch {
public:
static constexpr size_t MAX_SPRITES = 10000;
static constexpr size_t VERTICES_PER_SPRITE = 4;
static constexpr size_t INDICES_PER_SPRITE = 6;
struct Vertex {
glm::vec2 position;
glm::vec2 texCoord;
glm::vec4 color;
};
struct SpriteData {
glm::vec2 position;
glm::vec2 size;
glm::vec2 texCoordMin;
glm::vec2 texCoordMax;
glm::vec4 color;
float rotation;
glm::vec2 anchor;
bool isSDF = false;
};
GLSpriteBatch();
~GLSpriteBatch();
bool init();
void shutdown();
void begin(const glm::mat4& viewProjection);
void draw(const Texture& texture, const SpriteData& data);
void end();
// 统计
uint32_t getDrawCallCount() const { return drawCallCount_; }
uint32_t getSpriteCount() const { return spriteCount_; }
private:
GLuint vao_;
GLuint vbo_;
GLuint ibo_;
GLShader shader_;
std::vector<Vertex> vertices_;
std::vector<GLuint> indices_;
const Texture* currentTexture_;
bool currentIsSDF_;
glm::mat4 viewProjection_;
uint32_t drawCallCount_;
uint32_t spriteCount_;
void flush();
void setupShader();
};
} // namespace easy2d

View File

@ -1,124 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/core/color.h>
#include <easy2d/core/math_types.h>
#include <easy2d/core/string.h>
#include <glm/mat4x4.hpp>
namespace easy2d {
// 前向声明
class Window;
class Texture;
class FontAtlas;
class Shader;
// ============================================================================
// 渲染后端类型
// ============================================================================
enum class BackendType {
OpenGL,
// Vulkan,
// Metal,
// D3D11,
// D3D12
};
// ============================================================================
// 混合模式
// ============================================================================
enum class BlendMode {
None, // 不混合
Alpha, // 标准 Alpha 混合
Additive, // 加法混合
Multiply // 乘法混合
};
// ============================================================================
// 渲染后端抽象接口
// ============================================================================
class RenderBackend {
public:
virtual ~RenderBackend() = default;
// ------------------------------------------------------------------------
// 生命周期
// ------------------------------------------------------------------------
virtual bool init(Window* window) = 0;
virtual void shutdown() = 0;
// ------------------------------------------------------------------------
// 帧管理
// ------------------------------------------------------------------------
virtual void beginFrame(const Color& clearColor) = 0;
virtual void endFrame() = 0;
virtual void setViewport(int x, int y, int width, int height) = 0;
virtual void setVSync(bool enabled) = 0;
// ------------------------------------------------------------------------
// 状态设置
// ------------------------------------------------------------------------
virtual void setBlendMode(BlendMode mode) = 0;
virtual void setViewProjection(const glm::mat4& matrix) = 0;
// ------------------------------------------------------------------------
// 纹理
// ------------------------------------------------------------------------
virtual Ptr<Texture> createTexture(int width, int height, const uint8_t* pixels, int channels) = 0;
virtual Ptr<Texture> loadTexture(const std::string& filepath) = 0;
// ------------------------------------------------------------------------
// 精灵批渲染
// ------------------------------------------------------------------------
virtual void beginSpriteBatch() = 0;
virtual void drawSprite(const Texture& texture,
const Rect& destRect,
const Rect& srcRect,
const Color& tint,
float rotation,
const Vec2& anchor) = 0;
virtual void drawSprite(const Texture& texture,
const Vec2& position,
const Color& tint) = 0;
virtual void endSpriteBatch() = 0;
// ------------------------------------------------------------------------
// 形状渲染
// ------------------------------------------------------------------------
virtual void drawLine(const Vec2& start, const Vec2& end, const Color& color, float width = 1.0f) = 0;
virtual void drawRect(const Rect& rect, const Color& color, float width = 1.0f) = 0;
virtual void fillRect(const Rect& rect, const Color& color) = 0;
virtual void drawCircle(const Vec2& center, float radius, const Color& color, int segments = 32, float width = 1.0f) = 0;
virtual void fillCircle(const Vec2& center, float radius, const Color& color, int segments = 32) = 0;
virtual void drawTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3, const Color& color, float width = 1.0f) = 0;
virtual void fillTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3, const Color& color) = 0;
virtual void drawPolygon(const std::vector<Vec2>& points, const Color& color, float width = 1.0f) = 0;
virtual void fillPolygon(const std::vector<Vec2>& points, const Color& color) = 0;
// ------------------------------------------------------------------------
// 文字渲染
// ------------------------------------------------------------------------
virtual Ptr<FontAtlas> createFontAtlas(const std::string& filepath, int fontSize, bool useSDF = false) = 0;
virtual void drawText(const FontAtlas& font, const String& text, const Vec2& position, const Color& color) = 0;
virtual void drawText(const FontAtlas& font, const String& text, float x, float y, const Color& color) = 0;
// ------------------------------------------------------------------------
// 统计信息
// ------------------------------------------------------------------------
struct Stats {
uint32_t drawCalls = 0;
uint32_t triangleCount = 0;
uint32_t textureBinds = 0;
uint32_t shaderBinds = 0;
};
virtual Stats getStats() const = 0;
virtual void resetStats() = 0;
// ------------------------------------------------------------------------
// 工厂方法
// ------------------------------------------------------------------------
static UniquePtr<RenderBackend> create(BackendType type);
};
} // namespace easy2d

View File

@ -1,328 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/core/color.h>
#include <easy2d/graphics/texture.h>
#include <easy2d/graphics/opengl/gl_texture.h>
#include <mutex>
namespace easy2d {
// ============================================================================
// 渲染目标配置
// ============================================================================
struct RenderTargetConfig {
int width = 800; // 宽度
int height = 600; // 高度
PixelFormat colorFormat = PixelFormat::RGBA8; // 颜色格式
bool hasDepth = true; // 是否包含深度缓冲
bool hasDepthBuffer = true; // 兼容旧API的别名 (同hasDepth)
bool hasStencil = false; // 是否包含模板缓冲
int samples = 1; // 多重采样数 (1 = 无MSAA)
bool autoResize = true; // 是否自动调整大小
};
// ============================================================================
// 渲染目标 - 基于FBO的离屏渲染
// ============================================================================
class RenderTarget {
public:
RenderTarget();
~RenderTarget();
// 禁止拷贝
RenderTarget(const RenderTarget&) = delete;
RenderTarget& operator=(const RenderTarget&) = delete;
// 允许移动
RenderTarget(RenderTarget&& other) noexcept;
RenderTarget& operator=(RenderTarget&& other) noexcept;
// ------------------------------------------------------------------------
// 创建和销毁
// ------------------------------------------------------------------------
/**
* @brief
*/
bool create(const RenderTargetConfig& config);
/**
* @brief create的别名API
*/
bool init(const RenderTargetConfig& config) { return create(config); }
/**
* @brief
*/
bool createFromTexture(Ptr<Texture> texture, bool hasDepth = false);
/**
* @brief
*/
void destroy();
/**
* @brief destroy的别名API
*/
void shutdown() { destroy(); }
/**
* @brief
*/
bool isValid() const { return fbo_ != 0; }
// ------------------------------------------------------------------------
// 尺寸和格式
// ------------------------------------------------------------------------
int getWidth() const { return width_; }
int getHeight() const { return height_; }
Vec2 getSize() const { return Vec2(static_cast<float>(width_), static_cast<float>(height_)); }
PixelFormat getColorFormat() const { return colorFormat_; }
// ------------------------------------------------------------------------
// 绑定和解绑
// ------------------------------------------------------------------------
/**
* @brief
*/
void bind();
/**
* @brief
*/
void unbind();
/**
* @brief
*/
void clear(const Color& color = Colors::Transparent);
// ------------------------------------------------------------------------
// 纹理访问
// ------------------------------------------------------------------------
/**
* @brief
*/
Ptr<Texture> getColorTexture() const { return colorTexture_; }
/**
* @brief
*/
Ptr<Texture> getDepthTexture() const { return depthTexture_; }
// ------------------------------------------------------------------------
// 视口和裁剪
// ------------------------------------------------------------------------
/**
* @brief
*/
void setViewport(int x, int y, int width, int height);
/**
* @brief
*/
void getFullViewport(int& x, int& y, int& width, int& height) const;
// ------------------------------------------------------------------------
// 工具方法
// ------------------------------------------------------------------------
/**
* @brief
*/
bool resize(int width, int height);
/**
* @brief
*/
void copyTo(RenderTarget& target);
/**
* @brief blitTo的别名API
* @param target
* @param color
* @param depth
*/
void blitTo(RenderTarget& target, bool color = true, bool depth = false);
/**
* @brief
*/
void copyToScreen(int screenWidth, int screenHeight);
/**
* @brief
*/
bool saveToFile(const std::string& filepath);
// ------------------------------------------------------------------------
// 静态方法
// ------------------------------------------------------------------------
/**
* @brief
*/
static Ptr<RenderTarget> createFromConfig(const RenderTargetConfig& config);
/**
* @brief ID
*/
static GLuint getCurrentFBO();
/**
* @brief
*/
static void bindDefault();
/**
* @brief FBO ID使
*/
GLuint getFBO() const { return fbo_; }
protected:
GLuint fbo_ = 0; // 帧缓冲对象
GLuint rbo_ = 0; // 渲染缓冲对象(深度/模板)
Ptr<Texture> colorTexture_; // 颜色纹理
Ptr<Texture> depthTexture_; // 深度纹理(可选)
int width_ = 0;
int height_ = 0;
PixelFormat colorFormat_ = PixelFormat::RGBA8;
bool hasDepth_ = false;
bool hasStencil_ = false;
int samples_ = 1;
bool createFBO();
void deleteFBO();
};
// ============================================================================
// 多重采样渲染目标用于MSAA
// ============================================================================
class MultisampleRenderTarget : public RenderTarget {
public:
/**
* @brief
*/
bool create(int width, int height, int samples = 4);
/**
* @brief
*/
void resolveTo(RenderTarget& target);
/**
* @brief
*/
void destroy();
private:
GLuint colorRBO_ = 0; // 多重采样颜色渲染缓冲
};
// ============================================================================
// 渲染目标栈(用于嵌套渲染)
// ============================================================================
class RenderTargetStack {
public:
static RenderTargetStack& getInstance();
/**
* @brief
*/
void push(RenderTarget* target);
/**
* @brief
*/
void pop();
/**
* @brief
*/
RenderTarget* getCurrent() const;
/**
* @brief
*/
size_t size() const;
/**
* @brief
*/
void clear();
private:
RenderTargetStack() = default;
~RenderTargetStack() = default;
std::vector<RenderTarget*> stack_;
mutable std::mutex mutex_;
};
// ============================================================================
// 渲染目标管理器 - 全局渲染目标管理
// ============================================================================
class RenderTargetManager {
public:
/**
* @brief
*/
static RenderTargetManager& getInstance();
/**
* @brief
* @param width
* @param height
*/
bool init(int width, int height);
/**
* @brief
*/
void shutdown();
/**
* @brief
*/
Ptr<RenderTarget> createRenderTarget(const RenderTargetConfig& config);
/**
* @brief
*/
RenderTarget* getDefaultRenderTarget() const { return defaultRenderTarget_.get(); }
/**
* @brief
*/
void resize(int width, int height);
/**
* @brief
*/
bool isInitialized() const { return initialized_; }
private:
RenderTargetManager() = default;
~RenderTargetManager() = default;
RenderTargetManager(const RenderTargetManager&) = delete;
RenderTargetManager& operator=(const RenderTargetManager&) = delete;
Ptr<RenderTarget> defaultRenderTarget_;
std::vector<Ptr<RenderTarget>> renderTargets_;
bool initialized_ = false;
};
// ============================================================================
// 便捷宏
// ============================================================================
#define E2D_RENDER_TARGET_STACK() ::easy2d::RenderTargetStack::getInstance()
#define E2D_RENDER_TARGET_MANAGER() ::easy2d::RenderTargetManager::getInstance()
} // namespace easy2d

View File

@ -1,179 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/core/color.h>
#include <easy2d/graphics/opengl/gl_shader.h>
#include <string>
#include <unordered_map>
#include <functional>
namespace easy2d {
// ============================================================================
// Shader参数绑定回调
// ============================================================================
using ShaderBindCallback = std::function<void(GLShader&)>;
// ============================================================================
// Shader系统 - 管理所有Shader的加载、缓存和热重载
// ============================================================================
class ShaderSystem {
public:
// ------------------------------------------------------------------------
// 单例访问
// ------------------------------------------------------------------------
static ShaderSystem& getInstance();
// ------------------------------------------------------------------------
// 初始化和关闭
// ------------------------------------------------------------------------
bool init();
void shutdown();
// ------------------------------------------------------------------------
// Shader加载
// ------------------------------------------------------------------------
/**
* @brief Shader
* @param name Shader名称
* @param vertPath
* @param fragPath
* @return Shadernullptr
*/
Ptr<GLShader> loadFromFile(const std::string& name,
const std::string& vertPath,
const std::string& fragPath);
/**
* @brief Shader
* @param name Shader名称
* @param vertSource
* @param fragSource
* @return Shadernullptr
*/
Ptr<GLShader> loadFromSource(const std::string& name,
const std::string& vertSource,
const std::string& fragSource);
/**
* @brief Shader
* @param name Shader名称
* @return Shadernullptr
*/
Ptr<GLShader> get(const std::string& name);
/**
* @brief Shader是否存在
*/
bool has(const std::string& name) const;
// ------------------------------------------------------------------------
// Shader移除
// ------------------------------------------------------------------------
void remove(const std::string& name);
void clear();
// ------------------------------------------------------------------------
// 热重载支持
// ------------------------------------------------------------------------
/**
* @brief /
*/
void setFileWatching(bool enable);
bool isFileWatching() const { return fileWatching_; }
/**
* @brief
*/
void updateFileWatching();
/**
* @brief Shader
*/
bool reload(const std::string& name);
/**
* @brief Shader
*/
void reloadAll();
// ------------------------------------------------------------------------
// 内置Shader获取
// ------------------------------------------------------------------------
Ptr<GLShader> getBuiltinSpriteShader();
Ptr<GLShader> getBuiltinParticleShader();
Ptr<GLShader> getBuiltinPostProcessShader();
Ptr<GLShader> getBuiltinShapeShader();
// ------------------------------------------------------------------------
// 工具方法
// ------------------------------------------------------------------------
/**
* @brief
*/
static std::string readFile(const std::string& filepath);
/**
* @brief Shader文件的最后修改时间
*/
static uint64_t getFileModifiedTime(const std::string& filepath);
private:
ShaderSystem() = default;
~ShaderSystem() = default;
ShaderSystem(const ShaderSystem&) = delete;
ShaderSystem& operator=(const ShaderSystem&) = delete;
struct ShaderInfo {
Ptr<GLShader> shader;
std::string vertPath;
std::string fragPath;
uint64_t vertModifiedTime;
uint64_t fragModifiedTime;
bool isBuiltin;
};
std::unordered_map<std::string, ShaderInfo> shaders_;
bool fileWatching_ = false;
float watchTimer_ = 0.0f;
static constexpr float WATCH_INTERVAL = 1.0f; // 检查间隔(秒)
// 内置Shader缓存
Ptr<GLShader> builtinSpriteShader_;
Ptr<GLShader> builtinParticleShader_;
Ptr<GLShader> builtinPostProcessShader_;
Ptr<GLShader> builtinShapeShader_;
bool loadBuiltinShaders();
void checkAndReload();
};
// ============================================================================
// Shader参数包装器 - 简化Uniform设置
// ============================================================================
class ShaderParams {
public:
explicit ShaderParams(GLShader& shader);
ShaderParams& setBool(const std::string& name, bool value);
ShaderParams& setInt(const std::string& name, int value);
ShaderParams& setFloat(const std::string& name, float value);
ShaderParams& setVec2(const std::string& name, const glm::vec2& value);
ShaderParams& setVec3(const std::string& name, const glm::vec3& value);
ShaderParams& setVec4(const std::string& name, const glm::vec4& value);
ShaderParams& setMat4(const std::string& name, const glm::mat4& value);
ShaderParams& setColor(const std::string& name, const Color& color);
private:
GLShader& shader_;
};
// ============================================================================
// 便捷宏
// ============================================================================
#define E2D_SHADER_SYSTEM() ::easy2d::ShaderSystem::getInstance()
} // namespace easy2d

View File

@ -1,194 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/graphics/texture.h>
#include <string>
#include <unordered_map>
#include <list>
#include <functional>
#include <mutex>
namespace easy2d {
// ============================================================================
// 纹理池配置
// ============================================================================
struct TexturePoolConfig {
size_t maxCacheSize = 64 * 1024 * 1024; // 最大缓存大小 (64MB)
size_t maxTextureCount = 256; // 最大纹理数量
float unloadInterval = 30.0f; // 自动清理间隔 (秒)
bool enableAsyncLoad = true; // 启用异步加载
};
// ============================================================================
// 纹理池 - 使用LRU缓存策略管理纹理复用
// ============================================================================
class TexturePool {
public:
// ------------------------------------------------------------------------
// 单例访问
// ------------------------------------------------------------------------
static TexturePool& getInstance();
// ------------------------------------------------------------------------
// 初始化和关闭
// ------------------------------------------------------------------------
bool init(const TexturePoolConfig& config = TexturePoolConfig{});
void shutdown();
// ------------------------------------------------------------------------
// 纹理获取
// ------------------------------------------------------------------------
/**
* @brief
* @param filepath
* @return nullptr
*/
Ptr<Texture> get(const std::string& filepath);
/**
* @brief
* @param filepath
* @param callback
*/
void getAsync(const std::string& filepath,
std::function<void(Ptr<Texture>)> callback);
/**
* @brief
* @param name
* @param data
* @param width
* @param height
* @param format
* @return
*/
Ptr<Texture> createFromData(const std::string& name,
const uint8_t* data,
int width,
int height,
PixelFormat format = PixelFormat::RGBA8);
// ------------------------------------------------------------------------
// 缓存管理
// ------------------------------------------------------------------------
/**
* @brief
*/
void add(const std::string& key, Ptr<Texture> texture);
/**
* @brief
*/
void remove(const std::string& key);
/**
* @brief
*/
bool has(const std::string& key) const;
/**
* @brief
*/
void clear();
/**
* @brief 使LRU策略
* @param targetSize
*/
void trim(size_t targetSize);
// ------------------------------------------------------------------------
// 统计信息
// ------------------------------------------------------------------------
/**
* @brief
*/
size_t getTextureCount() const;
/**
* @brief
*/
size_t getCacheSize() const;
/**
* @brief
*/
float getHitRate() const;
/**
* @brief
*/
void printStats() const;
// ------------------------------------------------------------------------
// 自动清理
// ------------------------------------------------------------------------
/**
* @brief
*/
void update(float dt);
/**
* @brief
*/
void setAutoUnloadInterval(float interval);
private:
TexturePool() = default;
~TexturePool() = default;
TexturePool(const TexturePool&) = delete;
TexturePool& operator=(const TexturePool&) = delete;
// 缓存项
struct CacheEntry {
Ptr<Texture> texture;
size_t size; // 纹理大小(字节)
float lastAccessTime; // 最后访问时间
uint32_t accessCount; // 访问次数
};
// LRU列表最近使用的在前面
using LRUList = std::list<std::string>;
using LRUIterator = LRUList::iterator;
mutable std::mutex mutex_;
TexturePoolConfig config_;
// 缓存存储
std::unordered_map<std::string, CacheEntry> cache_;
std::unordered_map<std::string, LRUIterator> lruMap_;
LRUList lruList_;
// 统计
size_t totalSize_ = 0;
uint64_t hitCount_ = 0;
uint64_t missCount_ = 0;
float autoUnloadTimer_ = 0.0f;
bool initialized_ = false;
// 异步加载队列
struct AsyncLoadTask {
std::string filepath;
std::function<void(Ptr<Texture>)> callback;
};
std::vector<AsyncLoadTask> asyncTasks_;
// 内部方法
void touch(const std::string& key);
void evict();
Ptr<Texture> loadTexture(const std::string& filepath);
size_t calculateTextureSize(int width, int height, PixelFormat format);
void processAsyncTasks();
};
// ============================================================================
// 便捷宏
// ============================================================================
#define E2D_TEXTURE_POOL() ::easy2d::TexturePool::getInstance()
} // namespace easy2d

View File

@ -1,114 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/core/math_types.h>
#include <array>
#include <SDL.h>
namespace easy2d {
// ============================================================================
// 鼠标按钮枚举 (保留接口兼容性)
// ============================================================================
enum class MouseButton {
Left = 0,
Right = 1,
Middle = 2,
Button4 = 3,
Button5 = 4,
Button6 = 5,
Button7 = 6,
Button8 = 7,
Count = 8
};
// ============================================================================
// Input 类 - SDL2 GameController + Touch 输入管理
// ============================================================================
class Input {
public:
Input();
~Input();
// 初始化 (使用 SDL2 GameController API)
void init();
void shutdown();
// 每帧更新
void update();
// ------------------------------------------------------------------------
// 键盘输入 (映射到手柄按钮)
// ------------------------------------------------------------------------
bool isKeyDown(int keyCode) const;
bool isKeyPressed(int keyCode) const;
bool isKeyReleased(int keyCode) const;
// ------------------------------------------------------------------------
// 手柄按钮 (通过 SDL_GameController)
// ------------------------------------------------------------------------
bool isButtonDown(int button) const;
bool isButtonPressed(int button) const;
bool isButtonReleased(int button) const;
// 摇杆
Vec2 getLeftStick() const;
Vec2 getRightStick() const;
// ------------------------------------------------------------------------
// 鼠标输入 (映射到触摸屏)
// ------------------------------------------------------------------------
bool isMouseDown(MouseButton button) const;
bool isMousePressed(MouseButton button) const;
bool isMouseReleased(MouseButton button) const;
Vec2 getMousePosition() const;
Vec2 getMouseDelta() const;
float getMouseScroll() const { return 0.0f; }
float getMouseScrollDelta() const { return 0.0f; }
void setMousePosition(const Vec2& position);
void setMouseVisible(bool visible);
void setMouseLocked(bool locked);
// ------------------------------------------------------------------------
// 触摸屏
// ------------------------------------------------------------------------
bool isTouching() const { return touching_; }
Vec2 getTouchPosition() const { return touchPosition_; }
int getTouchCount() const { return touchCount_; }
// ------------------------------------------------------------------------
// 便捷方法
// ------------------------------------------------------------------------
bool isAnyKeyDown() const;
bool isAnyMouseDown() const;
private:
static constexpr int MAX_BUTTONS = SDL_CONTROLLER_BUTTON_MAX;
SDL_GameController* controller_;
// 手柄按钮状态
std::array<bool, MAX_BUTTONS> buttonsDown_;
std::array<bool, MAX_BUTTONS> prevButtonsDown_;
// 摇杆状态
float leftStickX_;
float leftStickY_;
float rightStickX_;
float rightStickY_;
// 触摸屏状态
bool touching_;
bool prevTouching_;
Vec2 touchPosition_;
Vec2 prevTouchPosition_;
int touchCount_;
// 映射键盘 keyCode 到 SDL GameController 按钮
SDL_GameControllerButton mapKeyToButton(int keyCode) const;
};
} // namespace easy2d

View File

@ -1,151 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/graphics/texture.h>
#include <easy2d/graphics/alpha_mask.h>
#include <easy2d/graphics/font.h>
#include <easy2d/audio/sound.h>
#include <string>
#include <vector>
#include <unordered_map>
#include <mutex>
namespace easy2d {
// ============================================================================
// 资源管理器 - 统一管理纹理、字体、音效等资源
// ============================================================================
class ResourceManager {
public:
// ------------------------------------------------------------------------
// 单例访问
// ------------------------------------------------------------------------
static ResourceManager& getInstance();
// ------------------------------------------------------------------------
// 搜索路径管理
// ------------------------------------------------------------------------
/// 添加资源搜索路径
void addSearchPath(const std::string& path);
/// 移除资源搜索路径
void removeSearchPath(const std::string& path);
/// 清空所有搜索路径
void clearSearchPaths();
/// 获取搜索路径列表
const std::vector<std::string>& getSearchPaths() const { return searchPaths_; }
/// 查找资源文件完整路径
std::string findResourcePath(const std::string& filename) const;
// ------------------------------------------------------------------------
// 纹理资源
// ------------------------------------------------------------------------
/// 加载纹理(带缓存)
Ptr<Texture> loadTexture(const std::string& filepath);
/// 加载纹理并生成Alpha遮罩用于不规则形状图片
Ptr<Texture> loadTextureWithAlphaMask(const std::string& filepath);
/// 通过key获取已缓存的纹理
Ptr<Texture> getTexture(const std::string& key) const;
/// 检查纹理是否已缓存
bool hasTexture(const std::string& key) const;
/// 卸载指定纹理
void unloadTexture(const std::string& key);
// ------------------------------------------------------------------------
// Alpha遮罩资源
// ------------------------------------------------------------------------
/// 获取纹理的Alpha遮罩如果已生成
const AlphaMask* getAlphaMask(const std::string& textureKey) const;
/// 为已加载的纹理生成Alpha遮罩
bool generateAlphaMask(const std::string& textureKey);
/// 检查纹理是否有Alpha遮罩
bool hasAlphaMask(const std::string& textureKey) const;
// ------------------------------------------------------------------------
// 字体图集资源
// ------------------------------------------------------------------------
/// 加载字体图集(带缓存)
Ptr<FontAtlas> loadFont(const std::string& filepath, int fontSize, bool useSDF = false);
/// 通过key获取已缓存的字体图集
Ptr<FontAtlas> getFont(const std::string& key) const;
/// 检查字体是否已缓存
bool hasFont(const std::string& key) const;
/// 卸载指定字体
void unloadFont(const std::string& key);
// ------------------------------------------------------------------------
// 音效资源
// ------------------------------------------------------------------------
/// 加载音效(带缓存)
Ptr<Sound> loadSound(const std::string& filepath);
Ptr<Sound> loadSound(const std::string& name, const std::string& filepath);
/// 通过key获取已缓存的音效
Ptr<Sound> getSound(const std::string& key) const;
/// 检查音效是否已缓存
bool hasSound(const std::string& key) const;
/// 卸载指定音效
void unloadSound(const std::string& key);
// ------------------------------------------------------------------------
// 缓存清理
// ------------------------------------------------------------------------
/// 清理所有失效的弱引用(自动清理已释放的资源)
void purgeUnused();
/// 清理指定类型的所有缓存
void clearTextureCache();
void clearFontCache();
void clearSoundCache();
/// 清理所有资源缓存
void clearAllCaches();
/// 获取各类资源的缓存数量
size_t getTextureCacheSize() const;
size_t getFontCacheSize() const;
size_t getSoundCacheSize() const;
ResourceManager();
~ResourceManager();
ResourceManager(const ResourceManager&) = delete;
ResourceManager& operator=(const ResourceManager&) = delete;
// 生成字体缓存key
std::string makeFontKey(const std::string& filepath, int fontSize, bool useSDF) const;
// 互斥锁保护缓存
mutable std::mutex textureMutex_;
mutable std::mutex fontMutex_;
mutable std::mutex soundMutex_;
// 搜索路径
std::vector<std::string> searchPaths_;
// 资源缓存 - 使用弱指针实现自动清理
std::unordered_map<std::string, WeakPtr<Texture>> textureCache_;
std::unordered_map<std::string, WeakPtr<FontAtlas>> fontCache_;
std::unordered_map<std::string, WeakPtr<Sound>> soundCache_;
};
} // namespace easy2d

View File

@ -1,194 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/core/math_types.h>
#include <easy2d/core/color.h>
#include <easy2d/graphics/render_backend.h>
#include <easy2d/event/event_dispatcher.h>
#include <vector>
#include <string>
#include <functional>
#include <algorithm>
namespace easy2d {
// 前向声明
class Scene;
class Action;
class RenderBackend;
struct RenderCommand;
// ============================================================================
// 节点基类 - 场景图的基础
// ============================================================================
class Node : public std::enable_shared_from_this<Node> {
public:
Node();
virtual ~Node();
// ------------------------------------------------------------------------
// 层级管理
// ------------------------------------------------------------------------
void addChild(Ptr<Node> child);
void removeChild(Ptr<Node> child);
void removeChildByName(const std::string& name);
void removeFromParent();
void removeAllChildren();
Ptr<Node> getParent() const { return parent_.lock(); }
const std::vector<Ptr<Node>>& getChildren() const { return children_; }
Ptr<Node> getChildByName(const std::string& name) const;
Ptr<Node> getChildByTag(int tag) const;
// ------------------------------------------------------------------------
// 变换属性
// ------------------------------------------------------------------------
void setPosition(const Vec2& pos);
void setPosition(float x, float y);
Vec2 getPosition() const { return position_; }
void setRotation(float degrees);
float getRotation() const { return rotation_; }
void setScale(const Vec2& scale);
void setScale(float scale);
void setScale(float x, float y);
Vec2 getScale() const { return scale_; }
void setAnchor(const Vec2& anchor);
void setAnchor(float x, float y);
Vec2 getAnchor() const { return anchor_; }
void setSkew(const Vec2& skew);
void setSkew(float x, float y);
Vec2 getSkew() const { return skew_; }
void setOpacity(float opacity);
float getOpacity() const { return opacity_; }
void setVisible(bool visible);
bool isVisible() const { return visible_; }
void setZOrder(int zOrder);
int getZOrder() const { return zOrder_; }
// ------------------------------------------------------------------------
// 世界变换
// ------------------------------------------------------------------------
Vec2 convertToWorldSpace(const Vec2& localPos) const;
Vec2 convertToNodeSpace(const Vec2& worldPos) const;
glm::mat4 getLocalTransform() const;
glm::mat4 getWorldTransform() const;
// ------------------------------------------------------------------------
// 名称和标签
// ------------------------------------------------------------------------
void setName(const std::string& name) { name_ = name; }
const std::string& getName() const { return name_; }
void setTag(int tag) { tag_ = tag; }
int getTag() const { return tag_; }
// ------------------------------------------------------------------------
// 生命周期回调
// ------------------------------------------------------------------------
virtual void onEnter();
virtual void onExit();
virtual void onUpdate(float dt);
virtual void onRender(RenderBackend& renderer);
virtual void onAttachToScene(Scene* scene);
virtual void onDetachFromScene();
// ------------------------------------------------------------------------
// 边界框(用于空间索引)
// ------------------------------------------------------------------------
virtual Rect getBoundingBox() const;
// 是否需要参与空间索引(默认 true
void setSpatialIndexed(bool indexed) { spatialIndexed_ = indexed; }
bool isSpatialIndexed() const { return spatialIndexed_; }
// 更新空间索引(手动调用,通常在边界框变化后)
void updateSpatialIndex();
// ------------------------------------------------------------------------
// 动作系统
// ------------------------------------------------------------------------
void runAction(Ptr<Action> action);
void stopAllActions();
void stopAction(Ptr<Action> action);
void stopActionByTag(int tag);
Ptr<Action> getActionByTag(int tag) const;
size_t getActionCount() const { return actions_.size(); }
// ------------------------------------------------------------------------
// 事件系统
// ------------------------------------------------------------------------
EventDispatcher& getEventDispatcher() { return eventDispatcher_; }
// ------------------------------------------------------------------------
// 内部方法
// ------------------------------------------------------------------------
void update(float dt);
void render(RenderBackend& renderer);
void sortChildren();
bool isRunning() const { return running_; }
Scene* getScene() const { return scene_; }
// 多线程渲染命令收集
virtual void collectRenderCommands(std::vector<RenderCommand>& commands, int parentZOrder = 0);
protected:
// 子类重写
virtual void onDraw(RenderBackend& renderer) {}
virtual void onUpdateNode(float dt) {}
virtual void generateRenderCommand(std::vector<RenderCommand>& commands, int zOrder) {};
// 供子类访问的内部状态
Vec2& getPositionRef() { return position_; }
Vec2& getScaleRef() { return scale_; }
Vec2& getAnchorRef() { return anchor_; }
float getRotationRef() { return rotation_; }
float getOpacityRef() { return opacity_; }
private:
// 层级
WeakPtr<Node> parent_;
std::vector<Ptr<Node>> children_;
bool childrenOrderDirty_ = false;
// 变换
Vec2 position_ = Vec2::Zero();
float rotation_ = 0.0f;
Vec2 scale_ = Vec2(1.0f, 1.0f);
Vec2 anchor_ = Vec2(0.5f, 0.5f);
Vec2 skew_ = Vec2::Zero();
float opacity_ = 1.0f;
bool visible_ = true;
int zOrder_ = 0;
// 缓存
mutable bool transformDirty_ = true;
mutable glm::mat4 localTransform_;
mutable glm::mat4 worldTransform_;
// 元数据
std::string name_;
int tag_ = -1;
// 状态
bool running_ = false;
Scene* scene_ = nullptr;
bool spatialIndexed_ = true; // 是否参与空间索引
Rect lastSpatialBounds_; // 上一次的空间索引边界(用于检测变化)
// 动作
std::vector<Ptr<Action>> actions_;
// 事件
EventDispatcher eventDispatcher_;
};
} // namespace easy2d

View File

@ -1,107 +0,0 @@
#pragma once
#include <easy2d/scene/node.h>
#include <easy2d/core/color.h>
#include <easy2d/graphics/camera.h>
#include <easy2d/spatial/spatial_manager.h>
#include <vector>
namespace easy2d {
// 前向声明
struct RenderCommand;
// ============================================================================
// 场景类 - 节点容器,管理整个场景图
// ============================================================================
class Scene : public Node {
public:
Scene();
~Scene() override = default;
// ------------------------------------------------------------------------
// 场景属性
// ------------------------------------------------------------------------
void setBackgroundColor(const Color& color) { backgroundColor_ = color; }
Color getBackgroundColor() const { return backgroundColor_; }
// ------------------------------------------------------------------------
// 摄像机
// ------------------------------------------------------------------------
void setCamera(Ptr<Camera> camera);
Ptr<Camera> getCamera() const { return camera_; }
Camera* getActiveCamera() const {
return camera_ ? camera_.get() : defaultCamera_.get();
}
// ------------------------------------------------------------------------
// 视口和尺寸
// ------------------------------------------------------------------------
void setViewportSize(float width, float height);
void setViewportSize(const Size& size);
Size getViewportSize() const { return viewportSize_; }
float getWidth() const { return viewportSize_.width; }
float getHeight() const { return viewportSize_.height; }
// ------------------------------------------------------------------------
// 场景状态
// ------------------------------------------------------------------------
bool isPaused() const { return paused_; }
void pause() { paused_ = true; }
void resume() { paused_ = false; }
// ------------------------------------------------------------------------
// 渲染和更新
// ------------------------------------------------------------------------
void renderScene(RenderBackend& renderer);
void renderContent(RenderBackend& renderer);
void updateScene(float dt);
void collectRenderCommands(std::vector<RenderCommand>& commands);
// ------------------------------------------------------------------------
// 空间索引系统
// ------------------------------------------------------------------------
SpatialManager& getSpatialManager() { return spatialManager_; }
const SpatialManager& getSpatialManager() const { return spatialManager_; }
// 启用/禁用空间索引
void setSpatialIndexingEnabled(bool enabled) { spatialIndexingEnabled_ = enabled; }
bool isSpatialIndexingEnabled() const { return spatialIndexingEnabled_; }
// 节点空间索引管理(内部使用)
void updateNodeInSpatialIndex(Node* node, const Rect& oldBounds, const Rect& newBounds);
void removeNodeFromSpatialIndex(Node* node);
// 碰撞检测查询
std::vector<Node*> queryNodesInArea(const Rect& area) const;
std::vector<Node*> queryNodesAtPoint(const Vec2& point) const;
std::vector<std::pair<Node*, Node*>> queryCollisions() const;
// ------------------------------------------------------------------------
// 静态创建方法
// ------------------------------------------------------------------------
static Ptr<Scene> create();
protected:
void onEnter() override;
void onExit() override;
friend class SceneManager;
private:
Color backgroundColor_ = Colors::Black;
Size viewportSize_ = Size::Zero();
Ptr<Camera> camera_;
Ptr<Camera> defaultCamera_;
bool paused_ = false;
// 空间索引系统
SpatialManager spatialManager_;
bool spatialIndexingEnabled_ = true;
};
} // namespace easy2d

View File

@ -1,147 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/scene/scene.h>
#include <string>
#include <unordered_map>
#include <functional>
#include <stack>
#include <vector>
namespace easy2d {
// 前向声明
struct RenderCommand;
class Transition;
// ============================================================================
// 场景切换特效类型
// ============================================================================
enum class TransitionType {
None,
Fade,
SlideLeft,
SlideRight,
SlideUp,
SlideDown,
Scale,
Flip
};
// ============================================================================
// 场景管理器 - 管理场景的生命周期和切换
// ============================================================================
class SceneManager {
public:
using TransitionCallback = std::function<void()>;
// ------------------------------------------------------------------------
// 单例访问
// ------------------------------------------------------------------------
static SceneManager& getInstance();
// ------------------------------------------------------------------------
// 场景栈操作
// ------------------------------------------------------------------------
// 运行第一个场景
void runWithScene(Ptr<Scene> scene);
// 替换当前场景
void replaceScene(Ptr<Scene> scene);
void replaceScene(Ptr<Scene> scene, TransitionType transition, float duration = 0.5f);
// 压入新场景(当前场景暂停)
void pushScene(Ptr<Scene> scene);
void pushScene(Ptr<Scene> scene, TransitionType transition, float duration = 0.5f);
// 弹出当前场景(恢复上一个场景)
void popScene();
void popScene(TransitionType transition, float duration = 0.5f);
// 弹出到根场景
void popToRootScene();
void popToRootScene(TransitionType transition, float duration = 0.5f);
// 弹出到指定场景
void popToScene(const std::string& name);
void popToScene(const std::string& name, TransitionType transition, float duration = 0.5f);
// ------------------------------------------------------------------------
// 获取场景
// ------------------------------------------------------------------------
Ptr<Scene> getCurrentScene() const;
Ptr<Scene> getPreviousScene() const;
Ptr<Scene> getRootScene() const;
// 通过名称获取场景
Ptr<Scene> getSceneByName(const std::string& name) const;
// ------------------------------------------------------------------------
// 查询
// ------------------------------------------------------------------------
size_t getSceneCount() const { return sceneStack_.size(); }
bool isEmpty() const { return sceneStack_.empty(); }
bool hasScene(const std::string& name) const;
// ------------------------------------------------------------------------
// 更新和渲染
// ------------------------------------------------------------------------
void update(float dt);
void render(RenderBackend& renderer);
void collectRenderCommands(std::vector<RenderCommand>& commands);
// ------------------------------------------------------------------------
// 过渡控制
// ------------------------------------------------------------------------
bool isTransitioning() const { return isTransitioning_; }
void setTransitionCallback(TransitionCallback callback) { transitionCallback_ = callback; }
// ------------------------------------------------------------------------
// 清理
// ------------------------------------------------------------------------
void end();
void purgeCachedScenes();
public:
SceneManager() = default;
~SceneManager() = default;
SceneManager(const SceneManager&) = delete;
SceneManager& operator=(const SceneManager&) = delete;
// 场景切换(供 Application 使用)
void enterScene(Ptr<Scene> scene);
void enterScene(Ptr<Scene> scene, Ptr<class Transition> transition);
private:
void doSceneSwitch();
void startTransition(Ptr<Scene> from, Ptr<Scene> to, TransitionType type, float duration, Function<void()> stackAction);
void updateTransition(float dt);
void finishTransition();
void dispatchPointerEvents(Scene& scene);
std::stack<Ptr<Scene>> sceneStack_;
std::unordered_map<std::string, Ptr<Scene>> namedScenes_;
// Transition state
bool isTransitioning_ = false;
TransitionType currentTransition_ = TransitionType::None;
float transitionDuration_ = 0.0f;
float transitionElapsed_ = 0.0f;
Ptr<Scene> outgoingScene_;
Ptr<Scene> incomingScene_;
Ptr<Transition> activeTransition_;
Function<void()> transitionStackAction_;
TransitionCallback transitionCallback_;
// Next scene to switch to (queued during transition)
Ptr<Scene> nextScene_;
bool sendCleanupToScene_ = false;
Node* hoverTarget_ = nullptr;
Node* captureTarget_ = nullptr;
Vec2 lastPointerWorld_ = Vec2::Zero();
bool hasLastPointerWorld_ = false;
};
} // namespace easy2d

View File

@ -1,107 +0,0 @@
#pragma once
#include <easy2d/scene/node.h>
#include <easy2d/core/color.h>
#include <easy2d/core/math_types.h>
#include <vector>
namespace easy2d {
// ============================================================================
// 形状类型
// ============================================================================
enum class ShapeType {
Point,
Line,
Rect,
Circle,
Triangle,
Polygon
};
// ============================================================================
// 形状节点 - 用于绘制几何形状
// ============================================================================
class ShapeNode : public Node {
public:
ShapeNode();
~ShapeNode() override = default;
// ------------------------------------------------------------------------
// 静态创建方法
// ------------------------------------------------------------------------
static Ptr<ShapeNode> create();
// 点
static Ptr<ShapeNode> createPoint(const Vec2& pos, const Color& color);
// 线
static Ptr<ShapeNode> createLine(const Vec2& start, const Vec2& end,
const Color& color, float width = 1.0f);
// 矩形
static Ptr<ShapeNode> createRect(const Rect& rect, const Color& color,
float width = 1.0f);
static Ptr<ShapeNode> createFilledRect(const Rect& rect, const Color& color);
// 圆形
static Ptr<ShapeNode> createCircle(const Vec2& center, float radius,
const Color& color, int segments = 32,
float width = 1.0f);
static Ptr<ShapeNode> createFilledCircle(const Vec2& center, float radius,
const Color& color, int segments = 32);
// 三角形
static Ptr<ShapeNode> createTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3,
const Color& color, float width = 1.0f);
static Ptr<ShapeNode> createFilledTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3,
const Color& color);
// 多边形
static Ptr<ShapeNode> createPolygon(const std::vector<Vec2>& points,
const Color& color, float width = 1.0f);
static Ptr<ShapeNode> createFilledPolygon(const std::vector<Vec2>& points,
const Color& color);
// ------------------------------------------------------------------------
// 属性设置
// ------------------------------------------------------------------------
void setShapeType(ShapeType type) { shapeType_ = type; }
ShapeType getShapeType() const { return shapeType_; }
void setColor(const Color& color) { color_ = color; }
Color getColor() const { return color_; }
void setFilled(bool filled) { filled_ = filled; }
bool isFilled() const { return filled_; }
void setLineWidth(float width) { lineWidth_ = width; }
float getLineWidth() const { return lineWidth_; }
void setSegments(int segments) { segments_ = segments; }
int getSegments() const { return segments_; }
// ------------------------------------------------------------------------
// 点设置
// ------------------------------------------------------------------------
void setPoints(const std::vector<Vec2>& points);
const std::vector<Vec2>& getPoints() const { return points_; }
void addPoint(const Vec2& point);
void clearPoints();
Rect getBoundingBox() const override;
protected:
void onDraw(RenderBackend& renderer) override;
void generateRenderCommand(std::vector<RenderCommand>& commands, int zOrder) override;
private:
ShapeType shapeType_ = ShapeType::Rect;
Color color_ = Colors::White;
bool filled_ = false;
float lineWidth_ = 1.0f;
int segments_ = 32;
std::vector<Vec2> points_;
};
} // namespace easy2d

View File

@ -1,54 +0,0 @@
#pragma once
#include <easy2d/scene/node.h>
#include <easy2d/graphics/texture.h>
namespace easy2d {
// ============================================================================
// 精灵节点
// ============================================================================
class Sprite : public Node {
public:
Sprite();
explicit Sprite(Ptr<Texture> texture);
~Sprite() override = default;
// 纹理
void setTexture(Ptr<Texture> texture);
Ptr<Texture> getTexture() const { return texture_; }
// 纹理矩形 (用于图集)
void setTextureRect(const Rect& rect);
Rect getTextureRect() const { return textureRect_; }
// 颜色混合
void setColor(const Color& color);
Color getColor() const { return color_; }
// 翻转
void setFlipX(bool flip);
void setFlipY(bool flip);
bool isFlipX() const { return flipX_; }
bool isFlipY() const { return flipY_; }
// 静态创建方法
static Ptr<Sprite> create();
static Ptr<Sprite> create(Ptr<Texture> texture);
static Ptr<Sprite> create(Ptr<Texture> texture, const Rect& rect);
Rect getBoundingBox() const override;
protected:
void onDraw(RenderBackend& renderer) override;
void generateRenderCommand(std::vector<RenderCommand>& commands, int zOrder) override;
private:
Ptr<Texture> texture_;
Rect textureRect_;
Color color_ = Colors::White;
bool flipX_ = false;
bool flipY_ = false;
};
} // namespace easy2d

View File

@ -1,84 +0,0 @@
#pragma once
#include <easy2d/scene/node.h>
#include <easy2d/core/color.h>
#include <easy2d/core/string.h>
#include <easy2d/graphics/font.h>
namespace easy2d {
// ============================================================================
// 文字节点
// ============================================================================
class Text : public Node {
public:
Text();
explicit Text(const String& text);
~Text() override = default;
// ------------------------------------------------------------------------
// 文字内容
// ------------------------------------------------------------------------
void setText(const String& text);
const String& getText() const { return text_; }
// ------------------------------------------------------------------------
// 字体
// ------------------------------------------------------------------------
void setFont(Ptr<FontAtlas> font);
Ptr<FontAtlas> getFont() const { return font_; }
// ------------------------------------------------------------------------
// 文字属性
// ------------------------------------------------------------------------
void setTextColor(const Color& color);
Color getTextColor() const { return color_; }
void setFontSize(int size);
int getFontSize() const { return fontSize_; }
// ------------------------------------------------------------------------
// 对齐方式
// ------------------------------------------------------------------------
enum class Alignment {
Left,
Center,
Right
};
void setAlignment(Alignment align);
Alignment getAlignment() const { return alignment_; }
// ------------------------------------------------------------------------
// 尺寸计算
// ------------------------------------------------------------------------
Vec2 getTextSize() const;
float getLineHeight() const;
// ------------------------------------------------------------------------
// 静态创建方法
// ------------------------------------------------------------------------
static Ptr<Text> create();
static Ptr<Text> create(const String& text);
static Ptr<Text> create(const String& text, Ptr<FontAtlas> font);
Rect getBoundingBox() const override;
protected:
void onDraw(RenderBackend& renderer) override;
void generateRenderCommand(std::vector<RenderCommand>& commands, int zOrder) override;
private:
String text_;
Ptr<FontAtlas> font_;
Color color_ = Colors::White;
int fontSize_ = 16;
Alignment alignment_ = Alignment::Left;
mutable Vec2 cachedSize_ = Vec2::Zero();
mutable bool sizeDirty_ = true;
void updateCache() const;
};
} // namespace easy2d

View File

@ -1,140 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/core/color.h>
#include <easy2d/scene/scene.h>
#include <functional>
namespace easy2d {
// ============================================================================
// 过渡方向
// ============================================================================
enum class TransitionDirection {
Left,
Right,
Up,
Down
};
// ============================================================================
// 过渡效果基类
// ============================================================================
class Transition : public std::enable_shared_from_this<Transition> {
public:
using FinishCallback = std::function<void()>;
Transition(float duration);
virtual ~Transition() = default;
// 开始过渡
void start(Ptr<Scene> from, Ptr<Scene> to);
// 更新过渡进度
void update(float dt);
// 渲染过渡效果
virtual void render(RenderBackend& renderer);
// 是否完成
bool isFinished() const { return isFinished_; }
// 获取进度 [0, 1]
float getProgress() const { return progress_; }
// 获取淡入淡出进度 (0->1 for fade in, 1->0 for fade out)
float getFadeInAlpha() const;
float getFadeOutAlpha() const;
// 设置完成回调
void setFinishCallback(FinishCallback callback) { finishCallback_ = callback; }
// 获取源场景和目标场景
Ptr<Scene> getOutgoingScene() const { return outgoingScene_; }
Ptr<Scene> getIncomingScene() const { return incomingScene_; }
protected:
// 子类实现具体的渲染效果
virtual void onRenderTransition(RenderBackend& renderer, float progress) = 0;
// 过渡完成时调用
virtual void onFinish();
float duration_;
float elapsed_;
float progress_;
bool isFinished_;
bool isStarted_;
Ptr<Scene> outgoingScene_;
Ptr<Scene> incomingScene_;
FinishCallback finishCallback_;
};
// ============================================================================
// 淡入淡出过渡
// ============================================================================
class FadeTransition : public Transition {
public:
FadeTransition(float duration);
protected:
void onRenderTransition(RenderBackend& renderer, float progress) override;
};
// ============================================================================
// 滑动过渡
// ============================================================================
class SlideTransition : public Transition {
public:
SlideTransition(float duration, TransitionDirection direction);
protected:
void onRenderTransition(RenderBackend& renderer, float progress) override;
private:
TransitionDirection direction_;
};
// ============================================================================
// 缩放过渡
// ============================================================================
class ScaleTransition : public Transition {
public:
ScaleTransition(float duration);
protected:
void onRenderTransition(RenderBackend& renderer, float progress) override;
};
// ============================================================================
// 翻页过渡
// ============================================================================
class FlipTransition : public Transition {
public:
enum class Axis { Horizontal, Vertical };
FlipTransition(float duration, Axis axis = Axis::Horizontal);
protected:
void onRenderTransition(RenderBackend& renderer, float progress) override;
private:
Axis axis_;
};
// ============================================================================
// 马赛克/方块过渡
// ============================================================================
class BoxTransition : public Transition {
public:
BoxTransition(float duration, int divisions = 8);
protected:
void onRenderTransition(RenderBackend& renderer, float progress) override;
private:
int divisions_;
};
} // namespace easy2d

View File

@ -1,40 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <string>
#include <squirrel.h>
namespace easy2d {
class ScriptEngine {
public:
static ScriptEngine& getInstance();
ScriptEngine(const ScriptEngine&) = delete;
ScriptEngine& operator=(const ScriptEngine&) = delete;
bool initialize();
void shutdown();
bool executeString(const std::string& code);
bool executeFile(const std::string& filepath);
HSQUIRRELVM getVM() const { return vm_; }
bool isInitialized() const { return vm_ != nullptr; }
private:
ScriptEngine() = default;
~ScriptEngine();
bool compileAndRun(const std::string& source, const std::string& sourceName);
static void printFunc(HSQUIRRELVM vm, const SQChar* fmt, ...);
static void errorFunc(HSQUIRRELVM vm, const SQChar* fmt, ...);
static SQInteger errorHandler(HSQUIRRELVM vm);
static void compilerError(HSQUIRRELVM vm, const SQChar* desc,
const SQChar* source, SQInteger line, SQInteger column);
HSQUIRRELVM vm_ = nullptr;
};
} // namespace easy2d

View File

@ -1,33 +0,0 @@
#pragma once
#include <easy2d/scene/node.h>
#include <easy2d/script/script_engine.h>
#include <string>
namespace easy2d {
class ScriptNode : public Node {
public:
ScriptNode();
~ScriptNode() override;
static Ptr<ScriptNode> create(const std::string& scriptPath);
bool loadScript(const std::string& scriptPath);
const std::string& getScriptPath() const { return scriptPath_; }
void onEnter() override;
void onExit() override;
void onUpdate(float dt) override;
private:
bool callMethod(const char* name);
bool callMethodWithFloat(const char* name, float arg);
void pushSelf();
std::string scriptPath_;
HSQOBJECT scriptTable_;
bool tableValid_ = false;
};
} // namespace easy2d

View File

@ -1,252 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <squirrel.h>
#include <string>
namespace easy2d {
namespace sq {
// ============================================================================
// Type tag helpers — unique address per type
// ============================================================================
template<typename T>
inline SQUserPointer typeTag() {
static int tag;
return &tag;
}
// ============================================================================
// SqClassName trait — maps C++ types to Squirrel class names
// ============================================================================
template<typename T>
struct SqClassName {
static const char* name();
};
// ============================================================================
// push / get primitives
// ============================================================================
inline void push(HSQUIRRELVM vm, int v) { sq_pushinteger(vm, static_cast<SQInteger>(v)); }
inline void push(HSQUIRRELVM vm, float v) { sq_pushfloat(vm, static_cast<SQFloat>(v)); }
inline void push(HSQUIRRELVM vm, double v) { sq_pushfloat(vm, static_cast<SQFloat>(v)); }
inline void push(HSQUIRRELVM vm, bool v) { sq_pushbool(vm, v ? SQTrue : SQFalse); }
inline void push(HSQUIRRELVM vm, const char* v) { sq_pushstring(vm, v, -1); }
inline void push(HSQUIRRELVM vm, const std::string& v) { sq_pushstring(vm, v.c_str(), static_cast<SQInteger>(v.size())); }
inline void pushNull(HSQUIRRELVM vm) { sq_pushnull(vm); }
inline SQInteger getInt(HSQUIRRELVM vm, SQInteger idx) {
SQInteger val = 0;
sq_getinteger(vm, idx, &val);
return val;
}
inline SQFloat getFloat(HSQUIRRELVM vm, SQInteger idx) {
SQFloat val = 0;
if (SQ_FAILED(sq_getfloat(vm, idx, &val))) {
SQInteger ival = 0;
if (SQ_SUCCEEDED(sq_getinteger(vm, idx, &ival)))
val = static_cast<SQFloat>(ival);
}
return val;
}
inline bool getBool(HSQUIRRELVM vm, SQInteger idx) {
SQBool val = SQFalse;
sq_getbool(vm, idx, &val);
return val != SQFalse;
}
inline std::string getString(HSQUIRRELVM vm, SQInteger idx) {
const SQChar* str = nullptr;
sq_getstring(vm, idx, &str);
return str ? std::string(str) : std::string();
}
// ============================================================================
// Value type userdata helpers
// ============================================================================
template<typename T>
T* pushValueInstance(HSQUIRRELVM vm, const T& val) {
sq_pushroottable(vm);
sq_pushstring(vm, SqClassName<T>::name(), -1);
if (SQ_FAILED(sq_get(vm, -2))) {
sq_pop(vm, 1);
return nullptr;
}
if (SQ_FAILED(sq_createinstance(vm, -1))) {
sq_pop(vm, 2);
return nullptr;
}
T* ud = nullptr;
sq_getinstanceup(vm, -1, reinterpret_cast<SQUserPointer*>(&ud), nullptr, SQFalse);
if (ud) *ud = val;
sq_remove(vm, -2); // class
sq_remove(vm, -2); // roottable
return ud;
}
template<typename T>
T* getValueInstance(HSQUIRRELVM vm, SQInteger idx) {
T* ud = nullptr;
sq_getinstanceup(vm, idx, reinterpret_cast<SQUserPointer*>(&ud), typeTag<T>(), SQFalse);
return ud;
}
// ============================================================================
// Shared pointer bridge for reference types
// ============================================================================
template<typename T>
void pushPtr(HSQUIRRELVM vm, Ptr<T> ptr) {
if (!ptr) { sq_pushnull(vm); return; }
sq_pushroottable(vm);
sq_pushstring(vm, SqClassName<T>::name(), -1);
if (SQ_FAILED(sq_get(vm, -2))) {
sq_pop(vm, 1);
sq_pushnull(vm);
return;
}
if (SQ_FAILED(sq_createinstance(vm, -1))) {
sq_pop(vm, 2);
sq_pushnull(vm);
return;
}
auto* storage = new Ptr<T>(std::move(ptr));
sq_setinstanceup(vm, -1, storage);
sq_setreleasehook(vm, -1, [](SQUserPointer p, SQInteger) -> SQInteger {
delete static_cast<Ptr<T>*>(p);
return 0;
});
sq_remove(vm, -2); // class
sq_remove(vm, -2); // roottable
}
template<typename T>
Ptr<T> getPtr(HSQUIRRELVM vm, SQInteger idx) {
SQUserPointer up = nullptr;
sq_getinstanceup(vm, idx, &up, typeTag<T>(), SQFalse);
if (!up) return nullptr;
return *static_cast<Ptr<T>*>(up);
}
template<typename T>
T* getRawPtr(HSQUIRRELVM vm, SQInteger idx) {
auto p = getPtr<T>(vm, idx);
return p ? p.get() : nullptr;
}
// ============================================================================
// Singleton pointer (no release hook)
// ============================================================================
template<typename T>
void pushSingleton(HSQUIRRELVM vm, T* ptr, const char* className) {
sq_pushroottable(vm);
sq_pushstring(vm, className, -1);
if (SQ_FAILED(sq_get(vm, -2))) {
sq_pop(vm, 1);
sq_pushnull(vm);
return;
}
if (SQ_FAILED(sq_createinstance(vm, -1))) {
sq_pop(vm, 2);
sq_pushnull(vm);
return;
}
sq_setinstanceup(vm, -1, ptr);
sq_remove(vm, -2); // class
sq_remove(vm, -2); // roottable
}
template<typename T>
T* getSingleton(HSQUIRRELVM vm, SQInteger idx) {
SQUserPointer up = nullptr;
sq_getinstanceup(vm, idx, &up, nullptr, SQFalse);
return static_cast<T*>(up);
}
// ============================================================================
// ClassDef — fluent API for registering a class
// ============================================================================
struct ClassDef {
HSQUIRRELVM vm;
ClassDef(HSQUIRRELVM v, const char* name, const char* base = nullptr)
: vm(v) {
sq_pushroottable(vm);
sq_pushstring(vm, name, -1);
if (base) {
sq_pushstring(vm, base, -1);
if (SQ_FAILED(sq_get(vm, -3))) {
sq_newclass(vm, SQFalse);
} else {
sq_newclass(vm, SQTrue);
}
} else {
sq_newclass(vm, SQFalse);
}
}
ClassDef& setTypeTag(SQUserPointer tag) {
sq_settypetag(vm, -1, tag);
return *this;
}
ClassDef& method(const char* name, SQFUNCTION fn, SQInteger nparams = 0, const char* typemask = nullptr) {
sq_pushstring(vm, name, -1);
sq_newclosure(vm, fn, 0);
if (nparams > 0 && typemask)
sq_setparamscheck(vm, nparams, typemask);
sq_newslot(vm, -3, SQFalse);
return *this;
}
ClassDef& staticMethod(const char* name, SQFUNCTION fn, SQInteger nparams = 0, const char* typemask = nullptr) {
sq_pushstring(vm, name, -1);
sq_newclosure(vm, fn, 0);
if (nparams > 0 && typemask)
sq_setparamscheck(vm, nparams, typemask);
sq_newslot(vm, -3, SQTrue);
return *this;
}
template<typename T>
ClassDef& setValueType(SQFUNCTION constructor) {
sq_settypetag(vm, -1, typeTag<T>());
sq_setclassudsize(vm, -1, sizeof(T));
sq_pushstring(vm, "constructor", -1);
sq_newclosure(vm, constructor, 0);
sq_newslot(vm, -3, SQFalse);
return *this;
}
void commit() {
sq_newslot(vm, -3, SQFalse);
sq_pop(vm, 1);
}
};
// ============================================================================
// Register a table of integer constants
// ============================================================================
inline void registerConstTable(HSQUIRRELVM vm, const char* tableName,
const char* const* names, const SQInteger* values, int count) {
sq_pushroottable(vm);
sq_pushstring(vm, tableName, -1);
sq_newtable(vm);
for (int i = 0; i < count; ++i) {
sq_pushstring(vm, names[i], -1);
sq_pushinteger(vm, values[i]);
sq_newslot(vm, -3, SQFalse);
}
sq_newslot(vm, -3, SQFalse);
sq_pop(vm, 1);
}
} // namespace sq
} // namespace easy2d

View File

@ -1,23 +0,0 @@
#pragma once
#include <easy2d/script/sq_binding.h>
#include <easy2d/core/math_types.h>
#include <easy2d/core/color.h>
namespace easy2d {
namespace sq {
// SqClassName specializations for value types
template<> struct SqClassName<Vec2> { static const char* name() { return "Vec2"; } };
template<> struct SqClassName<Size> { static const char* name() { return "Size"; } };
template<> struct SqClassName<Rect> { static const char* name() { return "Rect"; } };
template<> struct SqClassName<Color> { static const char* name() { return "Color"; } };
void registerVec2(HSQUIRRELVM vm);
void registerSize(HSQUIRRELVM vm);
void registerRect(HSQUIRRELVM vm);
void registerColor(HSQUIRRELVM vm);
void registerValueTypes(HSQUIRRELVM vm);
} // namespace sq
} // namespace easy2d

View File

@ -1,54 +0,0 @@
#pragma once
#include <easy2d/spatial/spatial_index.h>
#include <array>
namespace easy2d {
class QuadTree : public ISpatialIndex {
public:
static constexpr int MAX_OBJECTS = 10;
static constexpr int MAX_LEVELS = 5;
struct QuadTreeNode {
Rect bounds;
int level;
std::vector<std::pair<Node*, Rect>> objects;
std::array<std::unique_ptr<QuadTreeNode>, 4> children;
QuadTreeNode(const Rect& bounds, int level);
bool contains(const Rect& rect) const;
bool intersects(const Rect& rect) const;
};
explicit QuadTree(const Rect& worldBounds);
~QuadTree() override = default;
void insert(Node* node, const Rect& bounds) override;
void remove(Node* node) override;
void update(Node* node, const Rect& newBounds) override;
std::vector<Node*> query(const Rect& area) const override;
std::vector<Node*> query(const Vec2& point) const override;
std::vector<std::pair<Node*, Node*>> queryCollisions() const override;
void clear() override;
size_t size() const override;
bool empty() const override;
void rebuild() override;
private:
void split(QuadTreeNode* node);
void insertIntoNode(QuadTreeNode* node, Node* object, const Rect& bounds);
void queryNode(const QuadTreeNode* node, const Rect& area, std::vector<Node*>& results) const;
void queryNode(const QuadTreeNode* node, const Vec2& point, std::vector<Node*>& results) const;
void collectCollisions(const QuadTreeNode* node, std::vector<std::pair<Node*, Node*>>& collisions) const;
bool removeFromNode(QuadTreeNode* node, Node* object);
std::unique_ptr<QuadTreeNode> root_;
Rect worldBounds_;
size_t objectCount_ = 0;
};
}

View File

@ -1,51 +0,0 @@
#pragma once
#include <easy2d/spatial/spatial_index.h>
#include <unordered_map>
#include <unordered_set>
namespace easy2d {
class SpatialHash : public ISpatialIndex {
public:
using CellKey = std::pair<int64_t, int64_t>;
struct CellKeyHash {
size_t operator()(const CellKey& key) const {
return std::hash<int64_t>()(key.first) ^ (std::hash<int64_t>()(key.second) << 1);
}
};
explicit SpatialHash(float cellSize = 64.0f);
~SpatialHash() override = default;
void insert(Node* node, const Rect& bounds) override;
void remove(Node* node) override;
void update(Node* node, const Rect& newBounds) override;
std::vector<Node*> query(const Rect& area) const override;
std::vector<Node*> query(const Vec2& point) const override;
std::vector<std::pair<Node*, Node*>> queryCollisions() const override;
void clear() override;
size_t size() const override;
bool empty() const override;
void rebuild() override;
void setCellSize(float cellSize);
float getCellSize() const { return cellSize_; }
private:
CellKey getCellKey(float x, float y) const;
void getCellsForRect(const Rect& rect, std::vector<CellKey>& cells) const;
void insertIntoCells(Node* node, const Rect& bounds);
void removeFromCells(Node* node, const Rect& bounds);
float cellSize_;
std::unordered_map<CellKey, std::unordered_set<Node*>, CellKeyHash> grid_;
std::unordered_map<Node*, Rect> objectBounds_;
size_t objectCount_ = 0;
};
}

View File

@ -1,44 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/core/math_types.h>
#include <vector>
#include <memory>
namespace easy2d {
class Node;
enum class SpatialStrategy {
Auto,
QuadTree,
SpatialHash
};
struct SpatialQueryResult {
Node* node;
Rect bounds;
};
class ISpatialIndex {
public:
virtual ~ISpatialIndex() = default;
virtual void insert(Node* node, const Rect& bounds) = 0;
virtual void remove(Node* node) = 0;
virtual void update(Node* node, const Rect& newBounds) = 0;
virtual std::vector<Node*> query(const Rect& area) const = 0;
virtual std::vector<Node*> query(const Vec2& point) const = 0;
virtual std::vector<std::pair<Node*, Node*>> queryCollisions() const = 0;
virtual void clear() = 0;
virtual size_t size() const = 0;
virtual bool empty() const = 0;
virtual void rebuild() = 0;
};
using SpatialIndexPtr = std::unique_ptr<ISpatialIndex>;
}

View File

@ -1,61 +0,0 @@
#pragma once
#include <easy2d/spatial/spatial_index.h>
#include <memory>
#include <functional>
namespace easy2d {
class SpatialManager {
public:
using QueryCallback = std::function<bool(Node*)>;
SpatialManager();
explicit SpatialManager(const Rect& worldBounds);
~SpatialManager() = default;
void setStrategy(SpatialStrategy strategy);
void setAutoThresholds(size_t quadTreeThreshold, size_t hashThreshold);
void setWorldBounds(const Rect& bounds);
Rect getWorldBounds() const { return worldBounds_; }
void insert(Node* node, const Rect& bounds);
void remove(Node* node);
void update(Node* node, const Rect& newBounds);
std::vector<Node*> query(const Rect& area) const;
std::vector<Node*> query(const Vec2& point) const;
std::vector<std::pair<Node*, Node*>> queryCollisions() const;
void query(const Rect& area, const QueryCallback& callback) const;
void query(const Vec2& point, const QueryCallback& callback) const;
void clear();
size_t size() const;
bool empty() const;
void rebuild();
void optimize();
SpatialStrategy getCurrentStrategy() const;
const char* getStrategyName() const;
static std::unique_ptr<ISpatialIndex> createIndex(SpatialStrategy strategy, const Rect& bounds);
private:
void selectOptimalStrategy();
SpatialStrategy currentStrategy_ = SpatialStrategy::Auto;
SpatialStrategy activeStrategy_ = SpatialStrategy::QuadTree;
std::unique_ptr<ISpatialIndex> index_;
Rect worldBounds_;
size_t quadTreeThreshold_ = 1000;
size_t hashThreshold_ = 5000;
mutable size_t queryCount_ = 0;
mutable size_t totalQueryTime_ = 0;
};
}

View File

@ -1,169 +0,0 @@
#pragma once
#include <easy2d/ui/widget.h>
#include <easy2d/graphics/font.h>
#include <easy2d/graphics/texture.h>
#include <easy2d/platform/window.h>
namespace easy2d {
// 图片缩放模式
enum class ImageScaleMode {
Original, // 使用原图大小
Stretch, // 拉伸填充
ScaleFit, // 等比缩放,保持完整显示
ScaleFill // 等比缩放,填充整个区域(可能裁剪)
};
// ============================================================================
// 基础按钮类
// ============================================================================
class Button : public Widget {
public:
Button();
~Button() override = default;
static Ptr<Button> create();
void setText(const String& text);
const String& getText() const { return text_; }
void setFont(Ptr<FontAtlas> font);
Ptr<FontAtlas> getFont() const { return font_; }
void setPadding(const Vec2& padding);
Vec2 getPadding() const { return padding_; }
void setTextColor(const Color& color);
Color getTextColor() const { return textColor_; }
// 纯色背景设置
void setBackgroundColor(const Color& normal, const Color& hover, const Color& pressed);
void setBorder(const Color& color, float width);
// 图片背景设置
void setBackgroundImage(Ptr<Texture> normal, Ptr<Texture> hover = nullptr, Ptr<Texture> pressed = nullptr);
void setBackgroundImageScaleMode(ImageScaleMode mode);
void setCustomSize(const Vec2& size);
void setCustomSize(float width, float height);
// 圆角矩形设置
void setCornerRadius(float radius);
float getCornerRadius() const { return cornerRadius_; }
void setRoundedCornersEnabled(bool enabled);
bool isRoundedCornersEnabled() const { return roundedCornersEnabled_; }
// 鼠标光标设置(悬停时显示的光标形状)
void setHoverCursor(CursorShape cursor);
CursorShape getHoverCursor() const { return hoverCursor_; }
// Alpha遮罩点击检测用于不规则形状按钮
void setUseAlphaMaskForHitTest(bool enabled);
bool isUseAlphaMaskForHitTest() const { return useAlphaMaskForHitTest_; }
void setOnClick(Function<void()> callback);
protected:
void onDraw(RenderBackend& renderer) override;
void drawBackgroundImage(RenderBackend& renderer, const Rect& rect);
void drawRoundedRect(RenderBackend& renderer, const Rect& rect, const Color& color, float radius);
void fillRoundedRect(RenderBackend& renderer, const Rect& rect, const Color& color, float radius);
Vec2 calculateImageSize(const Vec2& buttonSize, const Vec2& imageSize);
// 状态访问(供子类使用)
bool isHovered() const { return hovered_; }
bool isPressed() const { return pressed_; }
private:
String text_;
Ptr<FontAtlas> font_;
Vec2 padding_ = Vec2(10.0f, 6.0f);
// 文字颜色
Color textColor_ = Colors::White;
// 纯色背景
Color bgNormal_ = Color(0.2f, 0.2f, 0.2f, 1.0f);
Color bgHover_ = Color(0.28f, 0.28f, 0.28f, 1.0f);
Color bgPressed_ = Color(0.15f, 0.15f, 0.15f, 1.0f);
// 图片背景
Ptr<Texture> imgNormal_;
Ptr<Texture> imgHover_;
Ptr<Texture> imgPressed_;
ImageScaleMode scaleMode_ = ImageScaleMode::Original;
bool useImageBackground_ = false;
// 边框
Color borderColor_ = Color(0.6f, 0.6f, 0.6f, 1.0f);
float borderWidth_ = 1.0f;
// 圆角矩形
float cornerRadius_ = 8.0f; // 圆角半径
bool roundedCornersEnabled_ = false; // 默认关闭
// 鼠标光标
CursorShape hoverCursor_ = CursorShape::Hand; // 悬停时默认显示手型光标
bool cursorChanged_ = false; // 标记是否已改变光标
// Alpha遮罩点击检测用于不规则形状按钮
bool useAlphaMaskForHitTest_ = false; // 默认关闭
bool hovered_ = false;
bool pressed_ = false;
Function<void()> onClick_;
};
// ============================================================================
// 切换按钮 - 点击切换两种状态(支持图片和文字)
// ============================================================================
class ToggleImageButton : public Button {
public:
ToggleImageButton();
~ToggleImageButton() override = default;
static Ptr<ToggleImageButton> create();
// 设置两种状态的图片
void setStateImages(Ptr<Texture> stateOffNormal, Ptr<Texture> stateOnNormal,
Ptr<Texture> stateOffHover = nullptr, Ptr<Texture> stateOnHover = nullptr,
Ptr<Texture> stateOffPressed = nullptr, Ptr<Texture> stateOnPressed = nullptr);
// 设置两种状态的文字
void setStateText(const String& textOff, const String& textOn);
// 设置两种状态的文字颜色
void setStateTextColor(const Color& colorOff, const Color& colorOn);
// 获取/设置当前状态
bool isOn() const { return isOn_; }
void setOn(bool on);
void toggle();
// 设置状态改变回调
void setOnStateChange(Function<void(bool)> callback);
protected:
void onDraw(RenderBackend& renderer) override;
private:
// 状态图片
Ptr<Texture> imgOffNormal_, imgOnNormal_;
Ptr<Texture> imgOffHover_, imgOnHover_;
Ptr<Texture> imgOffPressed_, imgOnPressed_;
// 状态文字
String textOff_, textOn_;
bool useStateText_ = false;
// 状态文字颜色
Color textColorOff_ = Colors::White;
Color textColorOn_ = Colors::White;
bool useStateTextColor_ = false;
bool isOn_ = false;
Function<void(bool)> onStateChange_;
};
} // namespace easy2d

View File

@ -1,23 +0,0 @@
#pragma once
#include <easy2d/scene/node.h>
namespace easy2d {
class Widget : public Node {
public:
Widget();
~Widget() override = default;
void setSize(const Size& size);
void setSize(float width, float height);
Size getSize() const { return size_; }
Rect getBoundingBox() const override;
private:
Size size_ = Size::Zero();
};
} // namespace easy2d

View File

@ -1,70 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <string>
namespace easy2d {
// ============================================================================
// DataStore 类 - INI 文件数据持久化
// ============================================================================
class DataStore {
public:
DataStore();
~DataStore();
/// 加载 INI 文件
bool load(const std::string& filename);
/// 保存到 INI 文件
bool save(const std::string& filename);
/// 获取字符串值
std::string getString(const std::string& section, const std::string& key, const std::string& defaultValue = "");
/// 获取整数值
int getInt(const std::string& section, const std::string& key, int defaultValue = 0);
/// 获取浮点数值
float getFloat(const std::string& section, const std::string& key, float defaultValue = 0.0f);
/// 获取布尔值
bool getBool(const std::string& section, const std::string& key, bool defaultValue = false);
/// 设置字符串值
void setString(const std::string& section, const std::string& key, const std::string& value);
/// 设置整数值
void setInt(const std::string& section, const std::string& key, int value);
/// 设置浮点数值
void setFloat(const std::string& section, const std::string& key, float value);
/// 设置布尔值
void setBool(const std::string& section, const std::string& key, bool value);
/// 删除键
void removeKey(const std::string& section, const std::string& key);
/// 删除整个 section
void removeSection(const std::string& section);
/// 检查键是否存在
bool hasKey(const std::string& section, const std::string& key);
/// 检查 section 是否存在
bool hasSection(const std::string& section);
/// 清除所有数据
void clear();
/// 获取当前文件名
const std::string& getFilename() const { return filename_; }
private:
class Impl;
UniquePtr<Impl> impl_;
std::string filename_;
};
} // namespace easy2d

View File

@ -1,225 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <cstdio>
#include <cstdarg>
#include <string>
#include <sstream>
#include <type_traits>
namespace easy2d {
// ============================================================================
// 日志级别枚举
// ============================================================================
enum class LogLevel {
Trace = 0,
Debug = 1,
Info = 2,
Warn = 3,
Error = 4,
Fatal = 5,
Off = 6
};
// ============================================================================
// 简单的 fmt-style {} 格式化器
// ============================================================================
namespace detail {
// 将单个参数转为字符串
template<typename T>
inline std::string to_string_arg(const T& value) {
if constexpr (std::is_same_v<T, std::string>) {
return value;
} else if constexpr (std::is_same_v<T, const char*> || std::is_same_v<T, char*>) {
return value ? std::string(value) : std::string("(null)");
} else if constexpr (std::is_same_v<T, bool>) {
return value ? "true" : "false";
} else if constexpr (std::is_arithmetic_v<T>) {
// 对浮点数使用特殊格式
if constexpr (std::is_floating_point_v<T>) {
char buf[64];
snprintf(buf, sizeof(buf), "%.2f", static_cast<double>(value));
return buf;
} else {
return std::to_string(value);
}
} else {
std::ostringstream oss;
oss << value;
return oss.str();
}
}
// 格式化基础情况:没有更多参数
inline std::string format_impl(const char* fmt) {
std::string result;
while (*fmt) {
if (*fmt == '{' && *(fmt + 1) == '}') {
result += "{}"; // 无参数可替换,保留原样
fmt += 2;
} else {
result += *fmt;
++fmt;
}
}
return result;
}
// 格式化递归:替换第一个 {} 并递归处理剩余
template<typename T, typename... Args>
inline std::string format_impl(const char* fmt, const T& first, const Args&... rest) {
std::string result;
while (*fmt) {
if (*fmt == '{') {
// 检查 {:#x} 等格式说明符
if (*(fmt + 1) == '}') {
result += to_string_arg(first);
fmt += 2;
result += format_impl(fmt, rest...);
return result;
} else if (*(fmt + 1) == ':') {
// 跳过格式说明符直到 }
const char* end = fmt + 2;
while (*end && *end != '}') ++end;
if (*end == '}') {
// 检查是否是十六进制格式
std::string spec(fmt + 2, end);
if (spec.find('x') != std::string::npos || spec.find('X') != std::string::npos) {
if constexpr (std::is_integral_v<T>) {
char buf[32];
snprintf(buf, sizeof(buf), "0x%x", static_cast<unsigned int>(first));
result += buf;
} else {
result += to_string_arg(first);
}
} else if (spec.find('f') != std::string::npos || spec.find('.') != std::string::npos) {
if constexpr (std::is_arithmetic_v<T>) {
// 解析精度
int precision = 2;
auto dot = spec.find('.');
if (dot != std::string::npos) {
precision = 0;
for (size_t i = dot + 1; i < spec.size() && spec[i] >= '0' && spec[i] <= '9'; ++i) {
precision = precision * 10 + (spec[i] - '0');
}
}
char fmtbuf[16];
snprintf(fmtbuf, sizeof(fmtbuf), "%%.%df", precision);
char buf[64];
snprintf(buf, sizeof(buf), fmtbuf, static_cast<double>(first));
result += buf;
} else {
result += to_string_arg(first);
}
} else {
result += to_string_arg(first);
}
fmt = end + 1;
result += format_impl(fmt, rest...);
return result;
}
}
}
result += *fmt;
++fmt;
}
return result;
}
} // namespace detail
// 顶层格式化函数
template<typename... Args>
inline std::string e2d_format(const char* fmt, const Args&... args) {
return detail::format_impl(fmt, args...);
}
// 无参数版本
inline std::string e2d_format(const char* fmt) {
return std::string(fmt);
}
// ============================================================================
// Logger 类 - 简单 printf 日志
// ============================================================================
class Logger {
public:
static void init();
static void shutdown();
static void setLevel(LogLevel level);
static void setConsoleOutput(bool enable);
static void setFileOutput(const std::string& filename);
static LogLevel getLevel() { return level_; }
template<typename... Args>
static void log(LogLevel level, const char* fmt, const Args&... args) {
if (level < level_) return;
std::string msg = e2d_format(fmt, args...);
const char* levelStr = "";
switch (level) {
case LogLevel::Trace: levelStr = "TRACE"; break;
case LogLevel::Debug: levelStr = "DEBUG"; break;
case LogLevel::Info: levelStr = "INFO "; break;
case LogLevel::Warn: levelStr = "WARN "; break;
case LogLevel::Error: levelStr = "ERROR"; break;
case LogLevel::Fatal: levelStr = "FATAL"; break;
default: break;
}
printf("[%s] %s\n", levelStr, msg.c_str());
}
// 无参数版本
static void log(LogLevel level, const char* msg) {
if (level < level_) return;
const char* levelStr = "";
switch (level) {
case LogLevel::Trace: levelStr = "TRACE"; break;
case LogLevel::Debug: levelStr = "DEBUG"; break;
case LogLevel::Info: levelStr = "INFO "; break;
case LogLevel::Warn: levelStr = "WARN "; break;
case LogLevel::Error: levelStr = "ERROR"; break;
case LogLevel::Fatal: levelStr = "FATAL"; break;
default: break;
}
printf("[%s] %s\n", levelStr, msg);
}
private:
static LogLevel level_;
static bool initialized_;
};
// ============================================================================
// 日志宏
// ============================================================================
#ifdef E2D_DEBUG
#define E2D_LOG_TRACE(...) ::easy2d::Logger::log(::easy2d::LogLevel::Trace, __VA_ARGS__)
#define E2D_LOG_DEBUG(...) ::easy2d::Logger::log(::easy2d::LogLevel::Debug, __VA_ARGS__)
#else
#define E2D_LOG_TRACE(...)
#define E2D_LOG_DEBUG(...)
#endif
#define E2D_LOG_INFO(...) ::easy2d::Logger::log(::easy2d::LogLevel::Info, __VA_ARGS__)
#define E2D_LOG_WARN(...) ::easy2d::Logger::log(::easy2d::LogLevel::Warn, __VA_ARGS__)
#define E2D_LOG_ERROR(...) ::easy2d::Logger::log(::easy2d::LogLevel::Error, __VA_ARGS__)
#define E2D_LOG_FATAL(...) ::easy2d::Logger::log(::easy2d::LogLevel::Fatal, __VA_ARGS__)
// 简化的日志宏
#define E2D_INFO(...) E2D_LOG_INFO(__VA_ARGS__)
#define E2D_WARN(...) E2D_LOG_WARN(__VA_ARGS__)
#define E2D_ERROR(...) E2D_LOG_ERROR(__VA_ARGS__)
#define E2D_FATAL(...) E2D_LOG_FATAL(__VA_ARGS__)
#ifdef E2D_DEBUG
#define E2D_DEBUG_LOG(...) E2D_LOG_DEBUG(__VA_ARGS__)
#define E2D_TRACE(...) E2D_LOG_TRACE(__VA_ARGS__)
#else
#define E2D_DEBUG_LOG(...)
#define E2D_TRACE(...)
#endif
} // namespace easy2d

View File

@ -1,91 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <random>
namespace easy2d {
// ============================================================================
// Random 类 - 随机数生成器
// ============================================================================
class Random {
public:
/// 获取单例实例
static Random& getInstance();
/// 设置随机种子
void setSeed(uint32 seed);
/// 使用当前时间作为种子
void randomize();
/// 获取 [0, 1) 范围内的随机浮点数
float getFloat();
/// 获取 [min, max] 范围内的随机浮点数
float getFloat(float min, float max);
/// 获取 [0, max] 范围内的随机整数
int getInt(int max);
/// 获取 [min, max] 范围内的随机整数
int getInt(int min, int max);
/// 获取随机布尔值
bool getBool();
/// 获取随机布尔值(带概率)
bool getBool(float probability);
/// 获取指定范围内的随机角度(弧度)
float getAngle();
/// 获取 [-1, 1] 范围内的随机数(用于方向)
float getSigned();
private:
Random();
~Random() = default;
Random(const Random&) = delete;
Random& operator=(const Random&) = delete;
std::mt19937 generator_;
std::uniform_real_distribution<float> floatDist_;
};
// ============================================================================
// 便捷函数
// ============================================================================
/// 获取 [0, 1) 范围内的随机浮点数
inline float randomFloat() {
return Random::getInstance().getFloat();
}
/// 获取 [min, max] 范围内的随机浮点数
inline float randomFloat(float min, float max) {
return Random::getInstance().getFloat(min, max);
}
/// 获取 [0, max] 范围内的随机整数
inline int randomInt(int max) {
return Random::getInstance().getInt(max);
}
/// 获取 [min, max] 范围内的随机整数
inline int randomInt(int min, int max) {
return Random::getInstance().getInt(min, max);
}
/// 获取随机布尔值
inline bool randomBool() {
return Random::getInstance().getBool();
}
/// 获取随机布尔值(带概率)
inline bool randomBool(float probability) {
return Random::getInstance().getBool(probability);
}
} // namespace easy2d

View File

@ -1,99 +0,0 @@
#pragma once
#include <easy2d/core/types.h>
#include <chrono>
#include <map>
#include <vector>
#include <memory>
namespace easy2d {
// ============================================================================
// Timer 类 - 单次/重复计时器
// ============================================================================
class Timer {
public:
using Clock = std::chrono::steady_clock;
using TimePoint = Clock::time_point;
using Duration = Clock::duration;
using Callback = Function<void()>;
Timer(float interval, bool repeat, Callback callback);
/// 更新计时器,返回 true 如果触发了回调
bool update(float deltaTime);
/// 重置计时器
void reset();
/// 暂停计时器
void pause();
/// 恢复计时器
void resume();
/// 取消计时器(标记为无效)
void cancel();
/// 是否有效
bool isValid() const { return valid_; }
/// 是否暂停
bool isPaused() const { return paused_; }
/// 获取剩余时间(秒)
float getRemaining() const;
/// 获取唯一ID
uint32 getId() const { return id_; }
private:
uint32 id_;
float interval_;
float elapsed_;
bool repeat_;
bool paused_;
bool valid_;
Callback callback_;
static uint32 nextId_;
};
// ============================================================================
// TimerManager 类 - 管理所有计时器
// ============================================================================
class TimerManager {
public:
TimerManager() = default;
~TimerManager() = default;
/// 创建单次计时器返回计时器ID
uint32 addTimer(float delay, Timer::Callback callback);
/// 创建重复计时器返回计时器ID
uint32 addRepeatingTimer(float interval, Timer::Callback callback);
/// 取消指定ID的计时器
void cancelTimer(uint32 timerId);
/// 暂停指定ID的计时器
void pauseTimer(uint32 timerId);
/// 恢复指定ID的计时器
void resumeTimer(uint32 timerId);
/// 更新所有计时器(每帧调用)
void update(float deltaTime);
/// 清除所有计时器
void clear();
/// 获取计时器数量
size_t getTimerCount() const { return timers_.size(); }
private:
std::map<uint32, std::unique_ptr<Timer>> timers_;
std::vector<uint32> timersToRemove_;
};
} // namespace easy2d

View File

@ -1,85 +0,0 @@
#include "easy2d/action/action.h"
#include "easy2d/scene/node.h"
namespace easy2d {
Action::Action()
: elapsed_(0.0f)
, duration_(0.0f)
, speed_(1.0f)
, tag_(-1)
{
}
void Action::start(Node* target)
{
target_ = target;
originalTarget_ = target;
elapsed_ = 0.0f;
state_ = ActionState::Running;
onStart();
}
void Action::stop()
{
target_ = nullptr;
state_ = ActionState::Completed;
}
void Action::update(float dt)
{
if (state_ != ActionState::Running)
return;
step(dt);
if (isDone())
{
state_ = ActionState::Completed;
onComplete();
if (completionCallback_)
completionCallback_();
}
}
void Action::step(float dt)
{
if (state_ != ActionState::Running)
return;
elapsed_ += dt * speed_;
float progress = 0.0f;
if (duration_ > 0.0f)
{
progress = std::min(1.0f, elapsed_ / duration_);
}
else
{
progress = 1.0f;
}
if (progressCallback_)
progressCallback_(progress);
onUpdate(progress);
}
void Action::pause()
{
if (state_ == ActionState::Running)
state_ = ActionState::Paused;
}
void Action::resume()
{
if (state_ == ActionState::Paused)
state_ = ActionState::Running;
}
void Action::restart()
{
elapsed_ = 0.0f;
state_ = ActionState::Running;
onStart();
}
}

View File

@ -1,601 +0,0 @@
#include "easy2d/action/actions.h"
#include "easy2d/scene/node.h"
#include <algorithm>
namespace easy2d {
// IntervalAction
IntervalAction::IntervalAction(float duration)
{
duration_ = duration;
}
bool IntervalAction::isDone() const
{
return elapsed_ >= duration_;
}
// InstantAction
InstantAction::InstantAction()
{
duration_ = 0.0f;
}
bool InstantAction::isDone() const
{
return true;
}
// MoveBy
MoveBy::MoveBy(float duration, const Vec2& delta)
: IntervalAction(duration)
, delta_(delta)
{
}
void MoveBy::onStart()
{
startPosition_ = target_->getPosition();
}
void MoveBy::onUpdate(float progress)
{
Vec2 newPos = startPosition_ + delta_ * progress;
target_->setPosition(newPos);
}
Action* MoveBy::clone() const
{
return new MoveBy(duration_, delta_);
}
Action* MoveBy::reverse() const
{
return new MoveBy(duration_, -delta_);
}
// MoveTo
MoveTo::MoveTo(float duration, const Vec2& position)
: IntervalAction(duration)
, endPosition_(position)
{
}
void MoveTo::onStart()
{
startPosition_ = target_->getPosition();
delta_ = endPosition_ - startPosition_;
}
void MoveTo::onUpdate(float progress)
{
Vec2 newPos = startPosition_ + delta_ * progress;
target_->setPosition(newPos);
}
Action* MoveTo::clone() const
{
return new MoveTo(duration_, endPosition_);
}
Action* MoveTo::reverse() const
{
return new MoveTo(duration_, startPosition_);
}
// ScaleBy
ScaleBy::ScaleBy(float duration, float scale)
: IntervalAction(duration)
, deltaScale_(scale - 1.0f, scale - 1.0f)
{
}
ScaleBy::ScaleBy(float duration, float scaleX, float scaleY)
: IntervalAction(duration)
, deltaScale_(scaleX - 1.0f, scaleY - 1.0f)
{
}
ScaleBy::ScaleBy(float duration, const Vec2& scale)
: IntervalAction(duration)
, deltaScale_(scale.x - 1.0f, scale.y - 1.0f)
{
}
void ScaleBy::onStart()
{
startScale_ = target_->getScale();
}
void ScaleBy::onUpdate(float progress)
{
Vec2 newScale = startScale_ + deltaScale_ * progress;
target_->setScale(newScale);
}
Action* ScaleBy::clone() const
{
return new ScaleBy(duration_, Vec2(startScale_.x + deltaScale_.x, startScale_.y + deltaScale_.y));
}
Action* ScaleBy::reverse() const
{
return new ScaleBy(duration_, Vec2(startScale_.x - deltaScale_.x, startScale_.y - deltaScale_.y));
}
// ScaleTo
ScaleTo::ScaleTo(float duration, float scale)
: IntervalAction(duration)
, endScale_(scale, scale)
{
}
ScaleTo::ScaleTo(float duration, float scaleX, float scaleY)
: IntervalAction(duration)
, endScale_(scaleX, scaleY)
{
}
ScaleTo::ScaleTo(float duration, const Vec2& scale)
: IntervalAction(duration)
, endScale_(scale)
{
}
void ScaleTo::onStart()
{
startScale_ = target_->getScale();
delta_ = endScale_ - startScale_;
}
void ScaleTo::onUpdate(float progress)
{
Vec2 newScale = startScale_ + delta_ * progress;
target_->setScale(newScale);
}
Action* ScaleTo::clone() const
{
return new ScaleTo(duration_, endScale_);
}
Action* ScaleTo::reverse() const
{
return new ScaleTo(duration_, startScale_);
}
// RotateBy
RotateBy::RotateBy(float duration, float deltaAngle)
: IntervalAction(duration)
, deltaAngle_(deltaAngle)
{
}
RotateBy::RotateBy(float duration, float deltaAngleX, float deltaAngleY)
: IntervalAction(duration)
, deltaAngle_(deltaAngleX)
{
(void)deltaAngleY;
}
void RotateBy::onStart()
{
startAngle_ = target_->getRotation();
}
void RotateBy::onUpdate(float progress)
{
float newAngle = startAngle_ + deltaAngle_ * progress;
target_->setRotation(newAngle);
}
Action* RotateBy::clone() const
{
return new RotateBy(duration_, deltaAngle_);
}
Action* RotateBy::reverse() const
{
return new RotateBy(duration_, -deltaAngle_);
}
// RotateTo
RotateTo::RotateTo(float duration, float angle)
: IntervalAction(duration)
, endAngle_(angle)
{
}
RotateTo::RotateTo(float duration, float angleX, float angleY)
: IntervalAction(duration)
, endAngle_(angleX)
{
(void)angleY;
}
void RotateTo::onStart()
{
startAngle_ = target_->getRotation();
deltaAngle_ = endAngle_ - startAngle_;
// Shortest path
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);
}
Action* RotateTo::clone() const
{
return new RotateTo(duration_, endAngle_);
}
Action* RotateTo::reverse() const
{
return new RotateTo(duration_, startAngle_);
}
// FadeIn
FadeIn::FadeIn(float duration)
: IntervalAction(duration)
{
}
void FadeIn::onStart()
{
startOpacity_ = target_->getOpacity();
target_->setOpacity(0.0f);
}
void FadeIn::onUpdate(float progress)
{
target_->setOpacity(progress);
}
Action* FadeIn::clone() const
{
return new FadeIn(duration_);
}
Action* FadeIn::reverse() const
{
return new FadeOut(duration_);
}
// FadeOut
FadeOut::FadeOut(float duration)
: IntervalAction(duration)
{
}
void FadeOut::onStart()
{
startOpacity_ = target_->getOpacity();
target_->setOpacity(1.0f);
}
void FadeOut::onUpdate(float progress)
{
target_->setOpacity(1.0f - progress);
}
Action* FadeOut::clone() const
{
return new FadeOut(duration_);
}
Action* FadeOut::reverse() const
{
return new FadeIn(duration_);
}
// FadeTo
FadeTo::FadeTo(float duration, float opacity)
: IntervalAction(duration)
, endOpacity_(opacity)
{
}
void FadeTo::onStart()
{
startOpacity_ = target_->getOpacity();
deltaOpacity_ = endOpacity_ - startOpacity_;
}
void FadeTo::onUpdate(float progress)
{
target_->setOpacity(startOpacity_ + deltaOpacity_ * progress);
}
Action* FadeTo::clone() const
{
return new FadeTo(duration_, endOpacity_);
}
Action* FadeTo::reverse() const
{
return new FadeTo(duration_, startOpacity_);
}
// Sequence
Sequence::Sequence(const std::vector<Action*>& actions)
: IntervalAction(0.0f)
{
for (auto* action : actions)
{
if (action)
{
actions_.push_back(action->clone());
duration_ += action->getDuration();
}
}
}
Sequence::~Sequence()
{
for (auto* action : actions_)
delete action;
}
void Sequence::onStart()
{
currentIndex_ = 0;
split_ = 0.0f;
last_ = -1.0f;
if (!actions_.empty())
{
actions_[0]->start(target_);
}
}
void Sequence::onUpdate(float progress)
{
int found = 0;
float newTime = progress * duration_;
if (newTime < last_)
{
// Rewind
for (auto* action : actions_)
{
action->stop();
}
}
last_ = newTime;
for (size_t i = 0; i < actions_.size(); ++i)
{
split_ += actions_[i]->getDuration();
if (split_ > newTime)
{
found = static_cast<int>(i);
break;
}
else if (split_ == newTime)
{
found = static_cast<int>(i) + 1;
break;
}
}
if (found != currentIndex_)
{
if (currentIndex_ >= 0 && currentIndex_ < static_cast<int>(actions_.size()))
{
actions_[currentIndex_]->update(actions_[currentIndex_]->getDuration());
}
if (found >= 0 && found < static_cast<int>(actions_.size()))
{
actions_[found]->start(target_);
}
currentIndex_ = found;
}
if (currentIndex_ >= 0 && currentIndex_ < static_cast<int>(actions_.size()))
{
float localProgress = 0.0f;
if (actions_[currentIndex_]->getDuration() > 0.0f)
{
localProgress = (newTime - (split_ - actions_[currentIndex_]->getDuration())) / actions_[currentIndex_]->getDuration();
}
actions_[currentIndex_]->step(actions_[currentIndex_]->getDuration() * localProgress);
}
}
Action* Sequence::clone() const
{
return new Sequence(actions_);
}
Action* Sequence::reverse() const
{
std::vector<Action*> rev;
for (auto it = actions_.rbegin(); it != actions_.rend(); ++it)
{
rev.push_back((*it)->reverse());
}
return new Sequence(rev);
}
// Spawn
Spawn::Spawn(const std::vector<Action*>& actions)
: IntervalAction(0.0f)
{
for (auto* action : actions)
{
if (action)
{
actions_.push_back(action->clone());
duration_ = std::max(duration_, action->getDuration());
}
}
}
Spawn::~Spawn()
{
for (auto* action : actions_)
delete action;
}
void Spawn::onStart()
{
for (auto* action : actions_)
{
action->start(target_);
}
}
void Spawn::onUpdate(float progress)
{
for (auto* action : actions_)
{
float localProgress = 0.0f;
if (action->getDuration() > 0.0f)
{
localProgress = std::min(1.0f, (progress * duration_) / action->getDuration());
}
else
{
localProgress = 1.0f;
}
action->step(action->getDuration() * localProgress);
}
}
Action* Spawn::clone() const
{
return new Spawn(actions_);
}
Action* Spawn::reverse() const
{
std::vector<Action*> rev;
for (auto* action : actions_)
{
rev.push_back(action->reverse());
}
return new Spawn(rev);
}
// Loop
Loop::Loop(Action* action, int times)
: action_(action ? action->clone() : nullptr)
, times_(times)
, currentTimes_(0)
{
if (action_)
{
duration_ = times < 0 ? -1.0f : action_->getDuration() * times;
}
}
Loop::~Loop()
{
delete action_;
}
bool Loop::isDone() const
{
if (times_ < 0)
return false;
return currentTimes_ >= times_;
}
void Loop::onStart()
{
currentTimes_ = 0;
if (action_)
{
action_->start(target_);
}
}
void Loop::onUpdate(float progress)
{
if (!action_)
return;
float actionDuration = action_->getDuration();
float dt = progress * duration_ - elapsed_;
while (dt > 0.0f)
{
float localProgress = std::min(1.0f, dt / actionDuration);
action_->step(actionDuration * localProgress);
if (action_->isDone())
{
currentTimes_++;
if (times_ > 0 && currentTimes_ >= times_)
break;
action_->restart();
}
dt -= actionDuration;
}
}
Action* Loop::clone() const
{
return new Loop(action_, times_);
}
Action* Loop::reverse() const
{
return new Loop(action_ ? action_->reverse() : nullptr, times_);
}
// Delay
Delay::Delay(float duration)
: IntervalAction(duration)
{
}
void Delay::onUpdate(float progress)
{
// No update needed, just wait
}
Action* Delay::clone() const
{
return new Delay(duration_);
}
Action* Delay::reverse() const
{
return new Delay(duration_);
}
// CallFunc
CallFunc::CallFunc(Callback callback)
: callback_(std::move(callback))
{
}
void CallFunc::onUpdate(float progress)
{
(void)progress;
if (callback_)
callback_();
}
Action* CallFunc::clone() const
{
return new CallFunc(callback_);
}
Action* CallFunc::reverse() const
{
return new CallFunc(callback_);
}
}

View File

@ -1,224 +0,0 @@
#include <easy2d/action/ease.h>
#include <cmath>
namespace easy2d {
// Linear
float easeLinear(float t)
{
return t;
}
// Quadratic
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;
}
// Quartic
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;
}
// Quintic
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 * 3.14159265359f) / 2.0f);
}
float easeOutSine(float t)
{
return std::sin((t * 3.14159265359f) / 2.0f);
}
float easeInOutSine(float t)
{
return -(std::cos(3.14159265359f * 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 * 3.14159265359f) / 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 * 3.14159265359f) / 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 * 3.14159265359f) / 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;
}
}

View File

@ -1,43 +0,0 @@
#include <easy2d/animation/als_parser.h>
#include <easy2d/utils/logger.h>
namespace easy2d {
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 easy2d

View File

@ -1,317 +0,0 @@
#include <easy2d/animation/ani_binary_parser.h>
#include <easy2d/animation/sprite_frame_cache.h>
#include <easy2d/utils/logger.h>
#include <fstream>
#include <cstring>
#include <cassert>
namespace easy2d {
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 easy2d

View File

@ -1,415 +0,0 @@
#include <easy2d/animation/ani_parser.h>
#include <easy2d/animation/sprite_frame_cache.h>
#include <easy2d/utils/logger.h>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <cctype>
namespace easy2d {
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 easy2d

View File

@ -1,272 +0,0 @@
#include <easy2d/animation/animated_sprite.h>
#include <easy2d/animation/interpolation_engine.h>
namespace easy2d {
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 easy2d

View File

@ -1,102 +0,0 @@
#include <easy2d/animation/animation_cache.h>
#include <easy2d/animation/ani_parser.h>
#include <easy2d/animation/ani_binary_parser.h>
#include <easy2d/utils/logger.h>
#include <fstream>
namespace easy2d {
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 easy2d

View File

@ -1,200 +0,0 @@
#include <easy2d/animation/animation_controller.h>
#include <cassert>
namespace easy2d {
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 easy2d

View File

@ -1,259 +0,0 @@
#include <easy2d/animation/animation_node.h>
#include <easy2d/animation/interpolation_engine.h>
namespace easy2d {
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 easy2d

View File

@ -1,182 +0,0 @@
#include <easy2d/animation/composite_animation.h>
#include <easy2d/animation/animation_cache.h>
namespace easy2d {
// ============================================================================
// 静态工厂
// ============================================================================
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 easy2d

View File

@ -1,183 +0,0 @@
#include <easy2d/animation/frame_renderer.h>
#include <easy2d/graphics/texture.h>
namespace easy2d {
// ============================================================================
// 预加载
// ============================================================================
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;
Rect destRect{
{finalPos.x - w * 0.5f * flipScaleX, finalPos.y - h * 0.5f * flipScaleY},
{w, h}
};
Color finalColor{tint.r, tint.g, tint.b, tint.a * opacity};
renderer.drawSprite(*texture, destRect, srcRect, finalColor,
rotation, Vec2{0.5f, 0.5f});
}
} // namespace easy2d

View File

@ -1,322 +0,0 @@
#include <easy2d/app/application.h>
#include <easy2d/platform/window.h>
#include <easy2d/platform/input.h>
#include <easy2d/audio/audio_engine.h>
#include <easy2d/scene/scene_manager.h>
#include <easy2d/resource/resource_manager.h>
#include <easy2d/utils/timer.h>
#include <easy2d/event/event_queue.h>
#include <easy2d/event/event_dispatcher.h>
#include <easy2d/graphics/camera.h>
#include <easy2d/graphics/render_backend.h>
#include <easy2d/graphics/vram_manager.h>
#include <easy2d/utils/logger.h>
#include <switch.h>
#include <chrono>
#include <thread>
#include <time.h>
namespace easy2d {
// 获取当前时间(秒)
static double getTimeSeconds() {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return static_cast<double>(ts.tv_sec) + static_cast<double>(ts.tv_nsec) / 1000000000.0;
}
Application& Application::instance() {
static Application instance;
return instance;
}
Application::~Application() {
shutdown();
}
bool Application::init(const AppConfig& config) {
if (initialized_) {
E2D_LOG_WARN("Application already initialized");
return true;
}
config_ = config;
// ========================================
// 1. 初始化 RomFS 文件系统(必须在 SDL_Init 之前)
// ========================================
Result rc;
rc = romfsInit();
if (R_SUCCEEDED(rc)) {
E2D_LOG_INFO("RomFS initialized successfully");
} else {
E2D_LOG_WARN("romfsInit failed: {:#08X}, will use regular filesystem", rc);
}
// ========================================
// 2. 初始化 nxlink 调试输出(可选)
// ========================================
rc = socketInitializeDefault();
if (R_FAILED(rc)) {
E2D_LOG_WARN("socketInitializeDefault failed, nxlink will not be available");
}
// ========================================
// 3. 创建窗口(包含 SDL_Init + GLES 3.2 上下文创建)
// ========================================
window_ = makeUnique<Window>();
WindowConfig winConfig;
winConfig.title = config.title;
winConfig.width = 1280;
winConfig.height = 720;
winConfig.fullscreen = true;
winConfig.resizable = false;
winConfig.vsync = config.vsync;
winConfig.msaaSamples = config.msaaSamples;
if (!window_->create(winConfig)) {
E2D_LOG_ERROR("Failed to create window");
return false;
}
// ========================================
// 4. 初始化渲染器
// ========================================
renderer_ = RenderBackend::create(config.renderBackend);
if (!renderer_ || !renderer_->init(window_.get())) {
E2D_LOG_ERROR("Failed to initialize renderer");
window_->destroy();
return false;
}
// ========================================
// 5. 初始化其他子系统
// ========================================
sceneManager_ = makeUnique<SceneManager>();
resourceManager_ = makeUnique<ResourceManager>();
timerManager_ = makeUnique<TimerManager>();
eventQueue_ = makeUnique<EventQueue>();
eventDispatcher_ = makeUnique<EventDispatcher>();
camera_ = makeUnique<Camera>(0, static_cast<float>(window_->getWidth()),
static_cast<float>(window_->getHeight()), 0);
// 窗口大小回调Switch 上不会触发,但保留接口)
window_->setResizeCallback([this](int width, int height) {
if (camera_) {
camera_->setViewport(0, static_cast<float>(width),
static_cast<float>(height), 0);
}
if (sceneManager_) {
auto currentScene = sceneManager_->getCurrentScene();
if (currentScene) {
currentScene->setViewportSize(static_cast<float>(width),
static_cast<float>(height));
}
}
});
// 初始化音频引擎
AudioEngine::getInstance().initialize();
// 添加 romfs:/ 到资源搜索路径
resourceManager_->addSearchPath("romfs:/");
initialized_ = true;
running_ = true;
E2D_LOG_INFO("Application initialized successfully");
return true;
}
void Application::shutdown() {
if (!initialized_) return;
E2D_LOG_INFO("Shutting down application...");
// 打印 VRAM 统计
VRAMManager::getInstance().printStats();
// 清理子系统
sceneManager_.reset();
resourceManager_.reset();
timerManager_.reset();
eventQueue_.reset();
eventDispatcher_.reset();
camera_.reset();
// 关闭音频
AudioEngine::getInstance().shutdown();
// 关闭渲染器
if (renderer_) {
renderer_->shutdown();
renderer_.reset();
}
// 销毁窗口(包含 SDL_Quit
if (window_) {
window_->destroy();
window_.reset();
}
// Switch 清理
romfsExit();
socketExit();
initialized_ = false;
running_ = false;
E2D_LOG_INFO("Application shutdown complete");
}
void Application::run() {
if (!initialized_) {
E2D_LOG_ERROR("Application not initialized");
return;
}
lastFrameTime_ = getTimeSeconds();
// SDL2 on Switch 内部已处理 appletMainLoop
while (running_ && !window_->shouldClose()) {
mainLoop();
}
}
void Application::quit() {
shouldQuit_ = true;
running_ = false;
}
void Application::pause() {
if (!paused_) {
paused_ = true;
E2D_LOG_INFO("Application paused");
}
}
void Application::resume() {
if (paused_) {
paused_ = false;
lastFrameTime_ = getTimeSeconds();
E2D_LOG_INFO("Application resumed");
}
}
void Application::mainLoop() {
// 计算 delta time
double currentTime = getTimeSeconds();
deltaTime_ = static_cast<float>(currentTime - lastFrameTime_);
lastFrameTime_ = currentTime;
totalTime_ += deltaTime_;
// 计算 FPS
frameCount_++;
fpsTimer_ += deltaTime_;
if (fpsTimer_ >= 1.0f) {
currentFps_ = frameCount_;
frameCount_ = 0;
fpsTimer_ -= 1.0f;
}
// 处理窗口事件SDL_PollEvent + 输入更新)
window_->pollEvents();
// 处理事件队列
if (eventDispatcher_ && eventQueue_) {
eventDispatcher_->processQueue(*eventQueue_);
}
// 更新
if (!paused_) {
update();
}
// 渲染
render();
if (!config_.vsync && config_.fpsLimit > 0) {
double frameEndTime = getTimeSeconds();
double frameTime = frameEndTime - currentTime;
double target = 1.0 / static_cast<double>(config_.fpsLimit);
if (frameTime < target) {
auto sleepSeconds = target - frameTime;
std::this_thread::sleep_for(std::chrono::duration<double>(sleepSeconds));
}
}
}
void Application::update() {
if (timerManager_) {
timerManager_->update(deltaTime_);
}
if (sceneManager_) {
sceneManager_->update(deltaTime_);
}
}
void Application::render() {
if (!renderer_) {
E2D_LOG_ERROR("Render failed: renderer is null");
return;
}
renderer_->setViewport(0, 0, window_->getWidth(), window_->getHeight());
if (sceneManager_) {
sceneManager_->render(*renderer_);
} else {
E2D_LOG_WARN("Render: sceneManager is null");
}
window_->swapBuffers();
}
Input& Application::input() {
return *window_->getInput();
}
AudioEngine& Application::audio() {
return AudioEngine::getInstance();
}
SceneManager& Application::scenes() {
return *sceneManager_;
}
ResourceManager& Application::resources() {
return *resourceManager_;
}
TimerManager& Application::timers() {
return *timerManager_;
}
EventQueue& Application::eventQueue() {
return *eventQueue_;
}
EventDispatcher& Application::eventDispatcher() {
return *eventDispatcher_;
}
Camera& Application::camera() {
return *camera_;
}
void Application::enterScene(Ptr<Scene> scene) {
enterScene(scene, nullptr);
}
void Application::enterScene(Ptr<Scene> scene, Ptr<class Transition> transition) {
if (sceneManager_ && scene) {
scene->setViewportSize(static_cast<float>(window_->getWidth()),
static_cast<float>(window_->getHeight()));
sceneManager_->enterScene(scene, transition);
}
}
} // namespace easy2d

View File

@ -1,137 +0,0 @@
#include "easy2d/audio/audio_engine.h"
#include "easy2d/audio/sound.h"
#include "easy2d/utils/logger.h"
#include <SDL2/SDL.h>
#include <SDL2/SDL_mixer.h>
namespace easy2d {
AudioEngine& AudioEngine::getInstance() {
static AudioEngine instance;
return instance;
}
AudioEngine::~AudioEngine() {
shutdown();
}
bool AudioEngine::initialize() {
if (initialized_) {
return true;
}
// 初始化 SDL 音频子系统
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
E2D_LOG_ERROR("Failed to initialize SDL audio: {}", SDL_GetError());
return false;
}
// 打开音频设备
if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 4096) < 0) {
E2D_LOG_ERROR("Failed to open audio: {}", Mix_GetError());
SDL_QuitSubSystem(SDL_INIT_AUDIO);
return false;
}
// 分配混音通道
Mix_AllocateChannels(32);
initialized_ = true;
E2D_LOG_INFO("AudioEngine initialized successfully (SDL2_mixer)");
return true;
}
void AudioEngine::shutdown() {
if (!initialized_) {
return;
}
unloadAllSounds();
Mix_CloseAudio();
SDL_QuitSubSystem(SDL_INIT_AUDIO);
initialized_ = false;
E2D_LOG_INFO("AudioEngine shutdown");
}
std::shared_ptr<Sound> AudioEngine::loadSound(const std::string& filePath) {
return loadSound(filePath, filePath);
}
std::shared_ptr<Sound> AudioEngine::loadSound(const std::string& name, const std::string& filePath) {
if (!initialized_) {
E2D_LOG_ERROR("AudioEngine not initialized");
return nullptr;
}
// 检查是否已存在
auto it = sounds_.find(name);
if (it != sounds_.end()) {
return it->second;
}
Mix_Chunk* chunk = Mix_LoadWAV(filePath.c_str());
if (!chunk) {
E2D_LOG_ERROR("Failed to load sound: {} ({})", filePath, Mix_GetError());
return nullptr;
}
auto sound = std::shared_ptr<Sound>(new Sound(name, filePath, chunk));
sounds_[name] = sound;
E2D_LOG_DEBUG("Loaded sound: {}", filePath);
return sound;
}
std::shared_ptr<Sound> AudioEngine::getSound(const std::string& name) {
auto it = sounds_.find(name);
if (it != sounds_.end()) {
return it->second;
}
return nullptr;
}
void AudioEngine::unloadSound(const std::string& name) {
auto it = sounds_.find(name);
if (it != sounds_.end()) {
sounds_.erase(it);
E2D_LOG_DEBUG("Unloaded sound: {}", name);
}
}
void AudioEngine::unloadAllSounds() {
stopAll();
sounds_.clear();
E2D_LOG_DEBUG("Unloaded all sounds");
}
void AudioEngine::setMasterVolume(float volume) {
masterVolume_ = volume;
int mixVol = static_cast<int>(volume * MIX_MAX_VOLUME);
Mix_Volume(-1, mixVol); // 所有通道
Mix_VolumeMusic(mixVol); // 音乐
}
float AudioEngine::getMasterVolume() const {
return masterVolume_;
}
void AudioEngine::pauseAll() {
Mix_Pause(-1); // 暂停所有通道
}
void AudioEngine::resumeAll() {
Mix_Resume(-1); // 恢复所有通道
}
void AudioEngine::stopAll() {
for (auto& pair : sounds_) {
if (pair.second) {
pair.second->stop();
}
}
}
} // namespace easy2d

View File

@ -1,113 +0,0 @@
#include <easy2d/audio/sound.h>
#include <SDL2/SDL_mixer.h>
namespace easy2d {
Sound::Sound(const std::string& name, const std::string& filePath, Mix_Chunk* chunk)
: name_(name), filePath_(filePath), chunk_(chunk) {
}
Sound::~Sound() {
if (channel_ >= 0) {
Mix_HaltChannel(channel_);
channel_ = -1;
}
if (chunk_) {
Mix_FreeChunk(chunk_);
chunk_ = nullptr;
}
}
bool Sound::play() {
if (!chunk_) {
return false;
}
// 如果已在播放,先停止
if (channel_ >= 0 && Mix_Playing(channel_)) {
Mix_HaltChannel(channel_);
}
int loops = looping_ ? -1 : 0;
channel_ = Mix_PlayChannel(-1, chunk_, loops); // -1 = 自动分配通道
if (channel_ < 0) {
return false;
}
// 设置音量
int mixVol = static_cast<int>(volume_ * MIX_MAX_VOLUME);
Mix_Volume(channel_, mixVol);
return true;
}
void Sound::pause() {
if (channel_ >= 0) {
Mix_Pause(channel_);
}
}
void Sound::resume() {
if (channel_ >= 0) {
Mix_Resume(channel_);
}
}
void Sound::stop() {
if (channel_ >= 0) {
Mix_HaltChannel(channel_);
channel_ = -1;
}
}
bool Sound::isPlaying() const {
if (channel_ < 0) {
return false;
}
return Mix_Playing(channel_) && !Mix_Paused(channel_);
}
bool Sound::isPaused() const {
if (channel_ < 0) {
return false;
}
return Mix_Paused(channel_) != 0;
}
void Sound::setVolume(float volume) {
volume_ = volume;
if (channel_ >= 0) {
int mixVol = static_cast<int>(volume * MIX_MAX_VOLUME);
Mix_Volume(channel_, mixVol);
}
}
void Sound::setLooping(bool looping) {
looping_ = looping;
// SDL_mixer 的循环在播放时设置,运行中无法更改
// 如果需要即时生效,需要重新播放
}
void Sound::setPitch(float pitch) {
pitch_ = pitch;
// SDL2_mixer 不直接支持变速播放
// 需要更高级的音频处理才能实现
}
float Sound::getDuration() const {
// SDL2_mixer 没有直接获取时长的 API
// 返回 0 表示不支持
return 0.0f;
}
float Sound::getCursor() const {
// SDL2_mixer 没有直接获取播放位置的 API
return 0.0f;
}
void Sound::setCursor(float /*seconds*/) {
// SDL2_mixer 不支持 seek 到特定位置 (对 chunks)
}
} // namespace easy2d

View File

@ -1,218 +0,0 @@
#include <easy2d/core/string.h>
#include <cstring>
#include <cstdint>
namespace easy2d {
// ============================================================================
// GBK/GB2312 到 UTF-8 转换表
// 使用简化的转换表,覆盖常用中文字符
// ============================================================================
// GBK 编码范围:
// - 单字节0x00-0x7F (ASCII)
// - 双字节0x81-0xFE 0x40-0xFE
// 将 GBK 双字节解码为 Unicode 码点
// 这是简化的实现,使用 GBK 到 Unicode 的映射公式
static uint32_t gbkToUnicode(uint16_t gbkCode) {
// GBK 编码转 Unicode 的简化算法
// 对于常见汉字,使用近似转换
uint8_t high = (gbkCode >> 8) & 0xFF;
uint8_t low = gbkCode & 0xFF;
// ASCII 范围
if (high < 0x80) {
return high;
}
// GBK 双字节范围
if (high >= 0x81 && high <= 0xFE && low >= 0x40 && low <= 0xFE) {
// GBK 到 Unicode 的偏移计算(近似)
// GB2312 区域0xB0A1-0xF7FE -> Unicode 0x4E00-0x9FA5
if (high >= 0xB0 && high <= 0xF7 && low >= 0xA1 && low <= 0xFE) {
uint32_t area = high - 0xB0;
uint32_t pos = low - 0xA1;
return 0x4E00 + area * 94 + pos;
}
// 其他 GBK 区域使用线性映射
uint32_t gbkOffset = (high - 0x81) * 190 + (low - 0x40);
if (low > 0x7F) {
gbkOffset--;
}
// 映射到扩展 Unicode 区域
return 0x4E00 + (gbkOffset % 0x51A5);
}
// 无效字符返回替换字符
return 0xFFFD;
}
// 将 Unicode 码点编码为 UTF-8
static std::string unicodeToUtf8(uint32_t codepoint) {
std::string result;
if (codepoint <= 0x7F) {
// 1-byte
result.push_back(static_cast<char>(codepoint));
} else if (codepoint <= 0x7FF) {
// 2-byte
result.push_back(static_cast<char>(0xC0 | ((codepoint >> 6) & 0x1F)));
result.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
} else if (codepoint <= 0xFFFF) {
// 3-byte
result.push_back(static_cast<char>(0xE0 | ((codepoint >> 12) & 0x0F)));
result.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)));
result.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
} else if (codepoint <= 0x10FFFF) {
// 4-byte
result.push_back(static_cast<char>(0xF0 | ((codepoint >> 18) & 0x07)));
result.push_back(static_cast<char>(0x80 | ((codepoint >> 12) & 0x3F)));
result.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)));
result.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
}
return result;
}
// 将 UTF-8 解码为 Unicode 码点
// 返回解码后的码点和消耗的 UTF-8 字节数
static std::pair<uint32_t, size_t> utf8ToUnicode(const char* utf8) {
unsigned char byte = static_cast<unsigned char>(*utf8);
if ((byte & 0x80) == 0) {
// 1-byte
return {byte, 1};
} else if ((byte & 0xE0) == 0xC0) {
// 2-byte
uint32_t ch = (byte & 0x1F) << 6;
ch |= (static_cast<unsigned char>(utf8[1]) & 0x3F);
return {ch, 2};
} else if ((byte & 0xF0) == 0xE0) {
// 3-byte
uint32_t ch = (byte & 0x0F) << 12;
ch |= (static_cast<unsigned char>(utf8[1]) & 0x3F) << 6;
ch |= (static_cast<unsigned char>(utf8[2]) & 0x3F);
return {ch, 3};
} else if ((byte & 0xF8) == 0xF0) {
// 4-byte
uint32_t ch = (byte & 0x07) << 18;
ch |= (static_cast<unsigned char>(utf8[1]) & 0x3F) << 12;
ch |= (static_cast<unsigned char>(utf8[2]) & 0x3F) << 6;
ch |= (static_cast<unsigned char>(utf8[3]) & 0x3F);
return {ch, 4};
}
// Invalid UTF-8
return {0xFFFD, 1};
}
// 将 Unicode 码点编码为 GBK 双字节
static uint16_t unicodeToGbk(uint32_t unicode) {
// ASCII 范围
if (unicode <= 0x7F) {
return static_cast<uint16_t>(unicode);
}
// CJK Unified Ideographs (U+4E00 - U+9FA5) -> GB2312
if (unicode >= 0x4E00 && unicode <= 0x9FA5) {
uint32_t offset = unicode - 0x4E00;
uint32_t area = offset / 94;
uint32_t pos = offset % 94;
uint8_t high = static_cast<uint8_t>(0xB0 + area);
uint8_t low = static_cast<uint8_t>(0xA1 + pos);
return (static_cast<uint16_t>(high) << 8) | low;
}
// 其他字符无法转换,返回 '?'
return 0x3F;
}
// ============================================================================
// String 类 GBK 转换实现
// ============================================================================
String String::fromGBK(const char* gbk) {
if (!gbk || std::strlen(gbk) == 0) {
return String();
}
return fromGBK(std::string(gbk));
}
String String::fromGBK(const std::string& gbk) {
if (gbk.empty()) {
return String();
}
std::string utf8Result;
utf8Result.reserve(gbk.size() * 2); // 预估 UTF-8 可能比 GBK 大
size_t i = 0;
while (i < gbk.size()) {
unsigned char byte = static_cast<unsigned char>(gbk[i]);
if (byte < 0x80) {
// ASCII 字符,直接复制
utf8Result.push_back(static_cast<char>(byte));
i++;
} else if (i + 1 < gbk.size()) {
// GBK 双字节字符
uint8_t high = byte;
uint8_t low = static_cast<unsigned char>(gbk[i + 1]);
uint16_t gbkCode = (static_cast<uint16_t>(high) << 8) | low;
uint32_t unicode = gbkToUnicode(gbkCode);
utf8Result += unicodeToUtf8(unicode);
i += 2;
} else {
// 不完整的 GBK 字符,跳过
i++;
}
}
return String(utf8Result);
}
std::string String::toGBK() const {
if (data_.empty()) {
return std::string();
}
std::string gbkResult;
gbkResult.reserve(data_.size()); // 预估 GBK 可能更小或与 UTF-8 相当
size_t i = 0;
while (i < data_.size()) {
auto [unicode, bytes] = utf8ToUnicode(data_.c_str() + i);
if (unicode <= 0x7F) {
// ASCII 字符
gbkResult.push_back(static_cast<char>(unicode));
} else {
// 转换为 GBK 双字节
uint16_t gbkCode = unicodeToGbk(unicode);
if (gbkCode > 0xFF) {
// 双字节 GBK
gbkResult.push_back(static_cast<char>((gbkCode >> 8) & 0xFF));
gbkResult.push_back(static_cast<char>(gbkCode & 0xFF));
} else {
// 单字节ASCII
gbkResult.push_back(static_cast<char>(gbkCode));
}
}
i += bytes;
}
return gbkResult;
}
} // namespace easy2d

View File

@ -1,909 +0,0 @@
#include <easy2d/effects/custom_effect_manager.h>
#include <easy2d/utils/logger.h>
#include <json/json.hpp>
#include <fstream>
#include <sstream>
namespace easy2d {
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 easy2d

View File

@ -1,454 +0,0 @@
#include <easy2d/effects/particle_system.h>
#include <easy2d/graphics/render_backend.h>
#include <easy2d/utils/logger.h>
#include <cmath>
namespace easy2d {
// ============================================================================
// ParticleEmitter实现
// ============================================================================
ParticleEmitter::ParticleEmitter() : rng_(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;
// 计算目标矩形
float halfSize = p.size * 0.5f;
Rect destRect(
p.position.x - halfSize,
p.position.y - halfSize,
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) {
std::uniform_real_distribution<float> dist(min, max);
return dist(rng_);
}
Vec2 ParticleEmitter::randomPointInShape() {
switch (config_.shape) {
case EmitterConfig::Shape::Point:
return Vec2::Zero();
case EmitterConfig::Shape::Circle: {
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
);
}
case EmitterConfig::Shape::Rectangle:
return Vec2(
randomFloat(-config_.shapeSize.x * 0.5f, config_.shapeSize.x * 0.5f),
randomFloat(-config_.shapeSize.y * 0.5f, config_.shapeSize.y * 0.5f)
);
case EmitterConfig::Shape::Cone: {
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
);
}
}
return Vec2::Zero();
}
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 easy2d

View File

@ -1,380 +0,0 @@
#include <easy2d/effects/post_process.h>
#include <easy2d/graphics/render_target.h>
#include <easy2d/graphics/render_backend.h>
#include <easy2d/utils/logger.h>
// 使用标准 GLES3.2
#include <GLES3/gl32.h>
namespace easy2d {
// ============================================================================
// 静态成员初始化
// ============================================================================
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 easy2d

View File

@ -1,60 +0,0 @@
#include <easy2d/event/event.h>
namespace easy2d {
Event Event::createWindowResize(int width, int height) {
Event event;
event.type = EventType::WindowResize;
event.data = WindowResizeEvent{width, height};
return event;
}
Event Event::createWindowClose() {
Event event;
event.type = EventType::WindowClose;
return event;
}
Event Event::createKeyPress(int keyCode, int scancode, int mods) {
Event event;
event.type = EventType::KeyPressed;
event.data = KeyEvent{keyCode, scancode, mods};
return event;
}
Event Event::createKeyRelease(int keyCode, int scancode, int mods) {
Event event;
event.type = EventType::KeyReleased;
event.data = KeyEvent{keyCode, scancode, mods};
return event;
}
Event Event::createMouseButtonPress(int button, int mods, const Vec2& pos) {
Event event;
event.type = EventType::MouseButtonPressed;
event.data = MouseButtonEvent{button, mods, pos};
return event;
}
Event Event::createMouseButtonRelease(int button, int mods, const Vec2& pos) {
Event event;
event.type = EventType::MouseButtonReleased;
event.data = MouseButtonEvent{button, mods, pos};
return event;
}
Event Event::createMouseMove(const Vec2& pos, const Vec2& delta) {
Event event;
event.type = EventType::MouseMoved;
event.data = MouseMoveEvent{pos, delta};
return event;
}
Event Event::createMouseScroll(const Vec2& offset, const Vec2& pos) {
Event event;
event.type = EventType::MouseScrolled;
event.data = MouseScrollEvent{offset, pos};
return event;
}
} // namespace easy2d

View File

@ -1,69 +0,0 @@
#include <easy2d/event/event_dispatcher.h>
#include <easy2d/event/event_queue.h>
namespace easy2d {
EventDispatcher::EventDispatcher() : nextId_(1) {
}
ListenerId EventDispatcher::addListener(EventType type, EventCallback callback) {
ListenerId id = nextId_++;
listeners_[type].push_back({id, type, callback});
return id;
}
void EventDispatcher::removeListener(ListenerId id) {
for (auto& [type, listeners] : listeners_) {
auto it = std::remove_if(listeners.begin(), listeners.end(),
[id](const Listener& l) { return l.id == id; });
if (it != listeners.end()) {
listeners.erase(it, listeners.end());
return;
}
}
}
void EventDispatcher::removeAllListeners(EventType type) {
listeners_.erase(type);
}
void EventDispatcher::removeAllListeners() {
listeners_.clear();
}
void EventDispatcher::dispatch(Event& event) {
auto it = listeners_.find(event.type);
if (it != listeners_.end()) {
for (auto& listener : it->second) {
if (event.handled) break;
listener.callback(event);
}
}
}
void EventDispatcher::dispatch(const Event& event) {
Event mutableEvent = event;
dispatch(mutableEvent);
}
void EventDispatcher::processQueue(EventQueue& queue) {
Event event;
while (queue.poll(event)) {
dispatch(event);
}
}
size_t EventDispatcher::getListenerCount(EventType type) const {
auto it = listeners_.find(type);
return (it != listeners_.end()) ? it->second.size() : 0;
}
size_t EventDispatcher::getTotalListenerCount() const {
size_t count = 0;
for (const auto& [type, listeners] : listeners_) {
count += listeners.size();
}
return count;
}
} // namespace easy2d

View File

@ -1,53 +0,0 @@
#include <easy2d/event/event_queue.h>
namespace easy2d {
EventQueue::EventQueue() = default;
void EventQueue::push(const Event& event) {
std::lock_guard<std::mutex> lock(mutex_);
queue_.push(event);
}
void EventQueue::push(Event&& event) {
std::lock_guard<std::mutex> lock(mutex_);
queue_.push(std::move(event));
}
bool EventQueue::poll(Event& event) {
std::lock_guard<std::mutex> lock(mutex_);
if (queue_.empty()) {
return false;
}
event = queue_.front();
queue_.pop();
return true;
}
bool EventQueue::peek(Event& event) const {
std::lock_guard<std::mutex> lock(mutex_);
if (queue_.empty()) {
return false;
}
event = queue_.front();
return true;
}
void EventQueue::clear() {
std::lock_guard<std::mutex> lock(mutex_);
while (!queue_.empty()) {
queue_.pop();
}
}
bool EventQueue::empty() const {
std::lock_guard<std::mutex> lock(mutex_);
return queue_.empty();
}
size_t EventQueue::size() const {
std::lock_guard<std::mutex> lock(mutex_);
return queue_.size();
}
} // namespace easy2d

View File

@ -1,57 +0,0 @@
#include <easy2d/graphics/alpha_mask.h>
namespace easy2d {
AlphaMask::AlphaMask(int width, int height)
: width_(width)
, height_(height)
, data_(width * height, 255) {
}
AlphaMask AlphaMask::createFromPixels(const uint8_t* pixels, int width, int height, int channels) {
AlphaMask mask(width, height);
if (!pixels || width <= 0 || height <= 0) {
return mask;
}
// 根据通道数提取Alpha值
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
int pixelIndex = (y * width + x) * channels;
uint8_t alpha = 255;
if (channels == 4) {
// RGBA格式Alpha在第四个通道
alpha = pixels[pixelIndex + 3];
} else if (channels == 1) {
// 灰度图直接作为Alpha
alpha = pixels[pixelIndex];
} else if (channels == 3) {
// RGB格式没有Alpha通道视为不透明
alpha = 255;
}
mask.data_[y * width + x] = alpha;
}
}
return mask;
}
uint8_t AlphaMask::getAlpha(int x, int y) const {
if (!isValid(x, y)) {
return 0;
}
return data_[y * width_ + x];
}
bool AlphaMask::isOpaque(int x, int y, uint8_t threshold) const {
return getAlpha(x, y) >= threshold;
}
bool AlphaMask::isValid(int x, int y) const {
return x >= 0 && x < width_ && y >= 0 && y < height_;
}
} // namespace easy2d

View File

@ -1,162 +0,0 @@
#include <easy2d/graphics/camera.h>
#include <glm/gtc/matrix_transform.hpp>
#include <algorithm>
namespace easy2d {
Camera::Camera()
: left_(-1.0f), right_(1.0f), bottom_(-1.0f), top_(1.0f) {
}
Camera::Camera(float left, float right, float bottom, float top)
: left_(left), right_(right), bottom_(bottom), top_(top) {
}
Camera::Camera(const Size& viewport)
: left_(0.0f), right_(viewport.width), bottom_(viewport.height), top_(0.0f) {
}
void Camera::setPosition(const Vec2& position) {
position_ = position;
viewDirty_ = true;
}
void Camera::setPosition(float x, float y) {
position_.x = x;
position_.y = y;
viewDirty_ = true;
}
void Camera::setRotation(float degrees) {
rotation_ = degrees;
viewDirty_ = true;
}
void Camera::setZoom(float zoom) {
zoom_ = zoom;
viewDirty_ = true;
projDirty_ = true;
}
void Camera::setViewport(float left, float right, float bottom, float top) {
left_ = left;
right_ = right;
bottom_ = bottom;
top_ = top;
projDirty_ = true;
}
void Camera::setViewport(const Rect& rect) {
left_ = rect.left();
right_ = rect.right();
bottom_ = rect.bottom();
top_ = rect.top();
projDirty_ = true;
}
Rect Camera::getViewport() const {
return Rect(left_, top_, right_ - left_, bottom_ - top_);
}
glm::mat4 Camera::getViewMatrix() const {
if (viewDirty_) {
viewMatrix_ = glm::mat4(1.0f);
// 对于2D相机我们只需要平移注意Y轴方向
viewMatrix_ = glm::translate(viewMatrix_, glm::vec3(-position_.x, -position_.y, 0.0f));
viewDirty_ = false;
}
return viewMatrix_;
}
glm::mat4 Camera::getProjectionMatrix() const {
if (projDirty_) {
// 对于2D游戏Y轴向下增长屏幕坐标系
// OpenGL默认Y轴向上所以需要反转Y轴
// glm::ortho(left, right, bottom, top)
// 为了Y轴向下传入 (bottom=height, top=0)这样Y轴翻转
projMatrix_ = glm::ortho(
left_, right_, // X轴从左到右
bottom_, top_, // Y轴从下到上传入bottom>top实现Y轴向下增长
-1.0f, 1.0f
);
projDirty_ = false;
}
return projMatrix_;
}
glm::mat4 Camera::getViewProjectionMatrix() const {
// 对于2D相机我们主要依赖投影矩阵
// 视口变换已经处理了坐标系转换
return getProjectionMatrix();
}
Vec2 Camera::screenToWorld(const Vec2& screenPos) const {
// 屏幕坐标直接映射到世界坐标在2D中通常相同
return screenPos;
}
Vec2 Camera::worldToScreen(const Vec2& worldPos) const {
// 世界坐标直接映射到屏幕坐标在2D中通常相同
return worldPos;
}
Vec2 Camera::screenToWorld(float x, float y) const {
return screenToWorld(Vec2(x, y));
}
Vec2 Camera::worldToScreen(float x, float y) const {
return worldToScreen(Vec2(x, y));
}
void Camera::move(const Vec2& offset) {
position_ += offset;
viewDirty_ = true;
}
void Camera::move(float x, float y) {
position_.x += x;
position_.y += y;
viewDirty_ = true;
}
void Camera::setBounds(const Rect& bounds) {
bounds_ = bounds;
hasBounds_ = true;
}
void Camera::clearBounds() {
hasBounds_ = false;
}
void Camera::clampToBounds() {
if (!hasBounds_) return;
float viewportWidth = (right_ - left_) / zoom_;
float viewportHeight = (bottom_ - top_) / zoom_;
float minX = bounds_.left() + viewportWidth * 0.5f;
float maxX = bounds_.right() - viewportWidth * 0.5f;
float minY = bounds_.top() + viewportHeight * 0.5f;
float maxY = bounds_.bottom() - viewportHeight * 0.5f;
if (minX > maxX) {
position_.x = bounds_.center().x;
} else {
position_.x = std::clamp(position_.x, minX, maxX);
}
if (minY > maxY) {
position_.y = bounds_.center().y;
} else {
position_.y = std::clamp(position_.y, minY, maxY);
}
viewDirty_ = true;
}
void Camera::lookAt(const Vec2& target) {
position_ = target;
viewDirty_ = true;
}
} // namespace easy2d

View File

@ -1,259 +0,0 @@
#include <easy2d/graphics/opengl/gl_font_atlas.h>
#define STB_TRUETYPE_IMPLEMENTATION
#include <stb/stb_truetype.h>
#define STB_RECT_PACK_IMPLEMENTATION
#include <stb/stb_rect_pack.h>
#include <easy2d/utils/logger.h>
#include <fstream>
#include <algorithm>
namespace easy2d {
// ============================================================================
// 构造函数 - 初始化字体图集
// ============================================================================
GLFontAtlas::GLFontAtlas(const std::string& filepath, int fontSize, bool useSDF)
: fontSize_(fontSize)
, useSDF_(useSDF)
, currentY_(0)
, scale_(0.0f)
, ascent_(0.0f)
, descent_(0.0f)
, lineGap_(0.0f) {
// 加载字体文件
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
E2D_LOG_ERROR("Failed to load font: {}", filepath);
return;
}
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
fontData_.resize(size);
if (!file.read(reinterpret_cast<char*>(fontData_.data()), size)) {
E2D_LOG_ERROR("Failed to read font file: {}", filepath);
return;
}
// 初始化 stb_truetype
if (!stbtt_InitFont(&fontInfo_, fontData_.data(), stbtt_GetFontOffsetForIndex(fontData_.data(), 0))) {
E2D_LOG_ERROR("Failed to init font: {}", filepath);
return;
}
scale_ = stbtt_ScaleForPixelHeight(&fontInfo_, static_cast<float>(fontSize_));
int ascent, descent, lineGap;
stbtt_GetFontVMetrics(&fontInfo_, &ascent, &descent, &lineGap);
ascent_ = static_cast<float>(ascent) * scale_;
descent_ = static_cast<float>(descent) * scale_;
lineGap_ = static_cast<float>(lineGap) * scale_;
createAtlas();
}
// ============================================================================
// 析构函数
// ============================================================================
GLFontAtlas::~GLFontAtlas() = default;
// ============================================================================
// 获取字形 - 如果字形不存在则缓存它
// ============================================================================
const Glyph* GLFontAtlas::getGlyph(char32_t codepoint) const {
auto it = glyphs_.find(codepoint);
if (it == glyphs_.end()) {
cacheGlyph(codepoint);
it = glyphs_.find(codepoint);
}
return (it != glyphs_.end()) ? &it->second : nullptr;
}
// ============================================================================
// 测量文本尺寸
// ============================================================================
Vec2 GLFontAtlas::measureText(const String& text) {
float width = 0.0f;
float height = getAscent() - getDescent();
float currentWidth = 0.0f;
for (char32_t codepoint : text.toUtf32()) {
if (codepoint == '\n') {
width = std::max(width, currentWidth);
currentWidth = 0.0f;
height += getLineHeight();
continue;
}
const Glyph* glyph = getGlyph(codepoint);
if (glyph) {
currentWidth += glyph->advance;
}
}
width = std::max(width, currentWidth);
return Vec2(width, height);
}
// ============================================================================
// 创建图集纹理 - 初始化空白纹理和矩形打包上下文
// ============================================================================
void GLFontAtlas::createAtlas() {
int channels = useSDF_ ? 1 : 4;
std::vector<uint8_t> emptyData(ATLAS_WIDTH * ATLAS_HEIGHT * channels, 0);
texture_ = std::make_unique<GLTexture>(ATLAS_WIDTH, ATLAS_HEIGHT, emptyData.data(), channels);
texture_->setFilter(true);
// 初始化矩形打包上下文
packNodes_.resize(ATLAS_WIDTH);
stbrp_init_target(&packContext_, ATLAS_WIDTH, ATLAS_HEIGHT, packNodes_.data(), ATLAS_WIDTH);
}
// ============================================================================
// 缓存字形 - 渲染字形到图集并存储信息
// 使用 stb_rect_pack 进行矩形打包
// ============================================================================
void GLFontAtlas::cacheGlyph(char32_t codepoint) const {
int advance = 0;
stbtt_GetCodepointHMetrics(&fontInfo_, static_cast<int>(codepoint), &advance, nullptr);
float advancePx = advance * scale_;
if (useSDF_) {
constexpr int SDF_PADDING = 8;
constexpr unsigned char ONEDGE_VALUE = 128;
constexpr float PIXEL_DIST_SCALE = 64.0f;
int w = 0, h = 0, xoff = 0, yoff = 0;
unsigned char* sdf = stbtt_GetCodepointSDF(&fontInfo_,
scale_,
static_cast<int>(codepoint),
SDF_PADDING,
ONEDGE_VALUE,
PIXEL_DIST_SCALE,
&w, &h, &xoff, &yoff);
if (!sdf || w <= 0 || h <= 0) {
if (sdf) stbtt_FreeSDF(sdf, nullptr);
Glyph glyph{};
glyph.advance = advancePx;
glyphs_[codepoint] = glyph;
return;
}
stbrp_rect rect;
rect.id = static_cast<int>(codepoint);
rect.w = w + PADDING * 2;
rect.h = h + PADDING * 2;
stbrp_pack_rects(&packContext_, &rect, 1);
if (!rect.was_packed) {
E2D_LOG_WARN("Font atlas is full, cannot cache codepoint: {}", static_cast<int>(codepoint));
stbtt_FreeSDF(sdf, nullptr);
return;
}
int atlasX = rect.x + PADDING;
int atlasY = rect.y + PADDING;
Glyph glyph;
glyph.width = static_cast<float>(w);
glyph.height = static_cast<float>(h);
glyph.bearingX = static_cast<float>(xoff);
glyph.bearingY = static_cast<float>(yoff);
glyph.advance = advancePx;
// stb_rect_pack 使用左上角为原点OpenGL纹理使用左下角为原点
// 需要翻转V坐标
float v0 = static_cast<float>(atlasY) / ATLAS_HEIGHT;
float v1 = static_cast<float>(atlasY + h) / ATLAS_HEIGHT;
glyph.u0 = static_cast<float>(atlasX) / ATLAS_WIDTH;
glyph.v0 = 1.0f - v1; // 翻转V坐标
glyph.u1 = static_cast<float>(atlasX + w) / ATLAS_WIDTH;
glyph.v1 = 1.0f - v0; // 翻转V坐标
glyphs_[codepoint] = glyph;
glBindTexture(GL_TEXTURE_2D, texture_->getTextureID());
GLint prevUnpackAlignment = 4;
glGetIntegerv(GL_UNPACK_ALIGNMENT, &prevUnpackAlignment);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// OpenGL纹理坐标原点在左下角需要将Y坐标翻转
glTexSubImage2D(GL_TEXTURE_2D, 0, atlasX, ATLAS_HEIGHT - atlasY - h, w, h, GL_RED, GL_UNSIGNED_BYTE, sdf);
glPixelStorei(GL_UNPACK_ALIGNMENT, prevUnpackAlignment);
stbtt_FreeSDF(sdf, nullptr);
return;
}
int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
stbtt_GetCodepointBitmapBox(&fontInfo_, static_cast<int>(codepoint), scale_, scale_, &x0, &y0, &x1, &y1);
int w = x1 - x0;
int h = y1 - y0;
int xoff = x0;
int yoff = y0;
if (w <= 0 || h <= 0) {
Glyph glyph{};
glyph.advance = advancePx;
glyphs_[codepoint] = glyph;
return;
}
std::vector<unsigned char> bitmap(static_cast<size_t>(w) * static_cast<size_t>(h), 0);
stbtt_MakeCodepointBitmap(&fontInfo_, bitmap.data(), w, h, w, scale_, scale_, static_cast<int>(codepoint));
// 使用 stb_rect_pack 打包矩形
stbrp_rect rect;
rect.id = static_cast<int>(codepoint);
rect.w = w + PADDING * 2;
rect.h = h + PADDING * 2;
stbrp_pack_rects(&packContext_, &rect, 1);
if (!rect.was_packed) {
// 图集已满,无法缓存更多字形
E2D_LOG_WARN("Font atlas is full, cannot cache codepoint: {}", static_cast<int>(codepoint));
return;
}
int atlasX = rect.x + PADDING;
int atlasY = rect.y + PADDING;
// 创建字形信息
Glyph glyph;
glyph.width = static_cast<float>(w);
glyph.height = static_cast<float>(h);
glyph.bearingX = static_cast<float>(xoff);
glyph.bearingY = static_cast<float>(yoff);
glyph.advance = advancePx;
// 计算纹理坐标(相对于图集)
// stb_rect_pack 使用左上角为原点OpenGL纹理使用左下角为原点
// 需要翻转V坐标
float v0 = static_cast<float>(atlasY) / ATLAS_HEIGHT;
float v1 = static_cast<float>(atlasY + h) / ATLAS_HEIGHT;
glyph.u0 = static_cast<float>(atlasX) / ATLAS_WIDTH;
glyph.v0 = 1.0f - v1; // 翻转V坐标
glyph.u1 = static_cast<float>(atlasX + w) / ATLAS_WIDTH;
glyph.v1 = 1.0f - v0; // 翻转V坐标
// 存储字形
glyphs_[codepoint] = glyph;
// 将单通道字形数据转换为 RGBA 格式白色字形Alpha 通道存储灰度)
std::vector<uint8_t> rgbaData(w * h * 4);
for (int i = 0; i < w * h; ++i) {
uint8_t alpha = bitmap[static_cast<size_t>(i)];
rgbaData[i * 4 + 0] = 255; // R
rgbaData[i * 4 + 1] = 255; // G
rgbaData[i * 4 + 2] = 255; // B
rgbaData[i * 4 + 3] = alpha; // A
}
// 更新纹理 - 将字形数据上传到图集的指定位置
// OpenGL纹理坐标原点在左下角需要将Y坐标翻转
glBindTexture(GL_TEXTURE_2D, texture_->getTextureID());
glTexSubImage2D(GL_TEXTURE_2D, 0, atlasX, ATLAS_HEIGHT - atlasY - h, w, h, GL_RGBA, GL_UNSIGNED_BYTE, rgbaData.data());
}
} // namespace easy2d

View File

@ -1,412 +0,0 @@
#include <easy2d/graphics/opengl/gl_renderer.h>
#include <easy2d/graphics/opengl/gl_texture.h>
#include <easy2d/graphics/opengl/gl_font_atlas.h>
#include <easy2d/graphics/vram_manager.h>
#include <easy2d/platform/window.h>
#include <easy2d/utils/logger.h>
#include <SDL.h>
#include <cmath>
#include <vector>
#include <algorithm>
#include <cstring>
namespace easy2d {
// 形状渲染着色器 (GLES 3.2)
static const char* SHAPE_VERTEX_SHADER = R"(
#version 300 es
precision highp float;
layout(location = 0) in vec2 aPosition;
uniform mat4 uViewProjection;
void main() {
gl_Position = uViewProjection * vec4(aPosition, 0.0, 1.0);
}
)";
static const char* SHAPE_FRAGMENT_SHADER = R"(
#version 300 es
precision highp float;
uniform vec4 uColor;
out vec4 fragColor;
void main() {
fragColor = uColor;
}
)";
// VBO 初始大小(用于 VRAM 跟踪)
static constexpr size_t SHAPE_VBO_SIZE = 1024 * sizeof(float);
GLRenderer::GLRenderer() : window_(nullptr), shapeVao_(0), shapeVbo_(0), vsync_(true) {
resetStats();
}
GLRenderer::~GLRenderer() {
shutdown();
}
bool GLRenderer::init(Window* window) {
window_ = window;
// Switch: GL 上下文已通过 SDL2 + EGL 初始化,无需 glewInit()
// 初始化精灵批渲染器
if (!spriteBatch_.init()) {
E2D_LOG_ERROR("Failed to initialize sprite batch");
return false;
}
// 初始化形状渲染
initShapeRendering();
// 设置 OpenGL 状态
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
E2D_LOG_INFO("OpenGL Renderer initialized");
E2D_LOG_INFO("OpenGL Version: {}", reinterpret_cast<const char*>(glGetString(GL_VERSION)));
return true;
}
void GLRenderer::shutdown() {
spriteBatch_.shutdown();
if (shapeVbo_ != 0) {
glDeleteBuffers(1, &shapeVbo_);
VRAMManager::getInstance().freeBuffer(SHAPE_VBO_SIZE);
shapeVbo_ = 0;
}
if (shapeVao_ != 0) {
glDeleteVertexArrays(1, &shapeVao_);
shapeVao_ = 0;
}
}
void GLRenderer::beginFrame(const Color& clearColor) {
glClearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a);
glClear(GL_COLOR_BUFFER_BIT);
resetStats();
}
void GLRenderer::endFrame() {
// 交换缓冲区在 Window 类中处理
}
void GLRenderer::setViewport(int x, int y, int width, int height) {
glViewport(x, y, width, height);
}
void GLRenderer::setVSync(bool enabled) {
vsync_ = enabled;
// 使用 SDL2 设置交换间隔
SDL_GL_SetSwapInterval(enabled ? 1 : 0);
}
void GLRenderer::setBlendMode(BlendMode mode) {
switch (mode) {
case BlendMode::None:
glDisable(GL_BLEND);
break;
case BlendMode::Alpha:
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
break;
case BlendMode::Additive:
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
break;
case BlendMode::Multiply:
glEnable(GL_BLEND);
glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA);
break;
}
}
void GLRenderer::setViewProjection(const glm::mat4& matrix) {
viewProjection_ = matrix;
}
Ptr<Texture> GLRenderer::createTexture(int width, int height, const uint8_t* pixels, int channels) {
return makePtr<GLTexture>(width, height, pixels, channels);
}
Ptr<Texture> GLRenderer::loadTexture(const std::string& filepath) {
return makePtr<GLTexture>(filepath);
}
void GLRenderer::beginSpriteBatch() {
spriteBatch_.begin(viewProjection_);
}
void GLRenderer::drawSprite(const Texture& texture, const Rect& destRect, const Rect& srcRect,
const Color& tint, float rotation, const Vec2& anchor) {
GLSpriteBatch::SpriteData data;
data.position = glm::vec2(destRect.origin.x, destRect.origin.y);
data.size = glm::vec2(destRect.size.width, destRect.size.height);
Texture* tex = const_cast<Texture*>(&texture);
float texW = static_cast<float>(tex->getWidth());
float texH = static_cast<float>(tex->getHeight());
// 纹理坐标计算
float u1 = srcRect.origin.x / texW;
float u2 = (srcRect.origin.x + srcRect.size.width) / texW;
float v1 = 1.0f - (srcRect.origin.y / texH);
float v2 = 1.0f - ((srcRect.origin.y + srcRect.size.height) / texH);
data.texCoordMin = glm::vec2(glm::min(u1, u2), glm::min(v1, v2));
data.texCoordMax = glm::vec2(glm::max(u1, u2), glm::max(v1, v2));
data.color = glm::vec4(tint.r, tint.g, tint.b, tint.a);
data.rotation = rotation * 3.14159f / 180.0f;
data.anchor = glm::vec2(anchor.x, anchor.y);
data.isSDF = false;
spriteBatch_.draw(texture, data);
}
void GLRenderer::drawSprite(const Texture& texture, const Vec2& position, const Color& tint) {
Rect destRect(position.x, position.y, static_cast<float>(texture.getWidth()),
static_cast<float>(texture.getHeight()));
Rect srcRect(0, 0, static_cast<float>(texture.getWidth()), static_cast<float>(texture.getHeight()));
drawSprite(texture, destRect, srcRect, tint, 0.0f, Vec2(0, 0));
}
void GLRenderer::endSpriteBatch() {
spriteBatch_.end();
stats_.drawCalls += spriteBatch_.getDrawCallCount();
}
void GLRenderer::drawLine(const Vec2& start, const Vec2& end, const Color& color, float width) {
glLineWidth(width);
shapeShader_.bind();
shapeShader_.setMat4("uViewProjection", viewProjection_);
shapeShader_.setVec4("uColor", glm::vec4(color.r, color.g, color.b, color.a));
float vertices[] = { start.x, start.y, end.x, end.y };
glBindBuffer(GL_ARRAY_BUFFER, shapeVbo_);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
glBindVertexArray(shapeVao_);
glDrawArrays(GL_LINES, 0, 2);
stats_.drawCalls++;
stats_.triangleCount += 1;
}
void GLRenderer::drawRect(const Rect& rect, const Color& color, float width) {
float x1 = rect.origin.x;
float y1 = rect.origin.y;
float x2 = rect.origin.x + rect.size.width;
float y2 = rect.origin.y + rect.size.height;
drawLine(Vec2(x1, y1), Vec2(x2, y1), color, width);
drawLine(Vec2(x2, y1), Vec2(x2, y2), color, width);
drawLine(Vec2(x2, y2), Vec2(x1, y2), color, width);
drawLine(Vec2(x1, y2), Vec2(x1, y1), color, width);
}
void GLRenderer::fillRect(const Rect& rect, const Color& color) {
shapeShader_.bind();
shapeShader_.setMat4("uViewProjection", viewProjection_);
shapeShader_.setVec4("uColor", glm::vec4(color.r, color.g, color.b, color.a));
float vertices[] = {
rect.origin.x, rect.origin.y,
rect.origin.x + rect.size.width, rect.origin.y,
rect.origin.x + rect.size.width, rect.origin.y + rect.size.height,
rect.origin.x, rect.origin.y + rect.size.height
};
glBindBuffer(GL_ARRAY_BUFFER, shapeVbo_);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
glBindVertexArray(shapeVao_);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
stats_.drawCalls++;
stats_.triangleCount += 2;
}
void GLRenderer::drawCircle(const Vec2& center, float radius, const Color& color, int segments, float width) {
std::vector<float> vertices;
vertices.reserve((segments + 1) * 2);
for (int i = 0; i <= segments; ++i) {
float angle = 2.0f * 3.14159f * i / segments;
vertices.push_back(center.x + radius * cosf(angle));
vertices.push_back(center.y + radius * sinf(angle));
}
glLineWidth(width);
shapeShader_.bind();
shapeShader_.setMat4("uViewProjection", viewProjection_);
shapeShader_.setVec4("uColor", glm::vec4(color.r, color.g, color.b, color.a));
glBindBuffer(GL_ARRAY_BUFFER, shapeVbo_);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_DYNAMIC_DRAW);
glBindVertexArray(shapeVao_);
glDrawArrays(GL_LINE_STRIP, 0, segments + 1);
stats_.drawCalls++;
}
void GLRenderer::fillCircle(const Vec2& center, float radius, const Color& color, int segments) {
std::vector<float> vertices;
vertices.reserve((segments + 2) * 2);
vertices.push_back(center.x);
vertices.push_back(center.y);
for (int i = 0; i <= segments; ++i) {
float angle = 2.0f * 3.14159f * i / segments;
vertices.push_back(center.x + radius * cosf(angle));
vertices.push_back(center.y + radius * sinf(angle));
}
shapeShader_.bind();
shapeShader_.setMat4("uViewProjection", viewProjection_);
shapeShader_.setVec4("uColor", glm::vec4(color.r, color.g, color.b, color.a));
glBindBuffer(GL_ARRAY_BUFFER, shapeVbo_);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_DYNAMIC_DRAW);
glBindVertexArray(shapeVao_);
glDrawArrays(GL_TRIANGLE_FAN, 0, segments + 2);
stats_.drawCalls++;
stats_.triangleCount += segments;
}
void GLRenderer::drawTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3, const Color& color, float width) {
drawLine(p1, p2, color, width);
drawLine(p2, p3, color, width);
drawLine(p3, p1, color, width);
}
void GLRenderer::fillTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3, const Color& color) {
shapeShader_.bind();
shapeShader_.setMat4("uViewProjection", viewProjection_);
shapeShader_.setVec4("uColor", glm::vec4(color.r, color.g, color.b, color.a));
float vertices[] = { p1.x, p1.y, p2.x, p2.y, p3.x, p3.y };
glBindBuffer(GL_ARRAY_BUFFER, shapeVbo_);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
glBindVertexArray(shapeVao_);
glDrawArrays(GL_TRIANGLES, 0, 3);
stats_.drawCalls++;
stats_.triangleCount += 1;
}
void GLRenderer::drawPolygon(const std::vector<Vec2>& points, const Color& color, float width) {
for (size_t i = 0; i < points.size(); ++i) {
drawLine(points[i], points[(i + 1) % points.size()], color, width);
}
}
void GLRenderer::fillPolygon(const std::vector<Vec2>& points, const Color& color) {
// 简化的三角形扇形填充
if (points.size() < 3) return;
shapeShader_.bind();
shapeShader_.setMat4("uViewProjection", viewProjection_);
shapeShader_.setVec4("uColor", glm::vec4(color.r, color.g, color.b, color.a));
std::vector<float> vertices;
for (const auto& p : points) {
vertices.push_back(p.x);
vertices.push_back(p.y);
}
glBindBuffer(GL_ARRAY_BUFFER, shapeVbo_);
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_DYNAMIC_DRAW);
glBindVertexArray(shapeVao_);
glDrawArrays(GL_TRIANGLE_FAN, 0, static_cast<GLsizei>(points.size()));
stats_.drawCalls++;
stats_.triangleCount += static_cast<uint32_t>(points.size() - 2);
}
Ptr<FontAtlas> GLRenderer::createFontAtlas(const std::string& filepath, int fontSize, bool useSDF) {
return makePtr<GLFontAtlas>(filepath, fontSize, useSDF);
}
void GLRenderer::drawText(const FontAtlas& font, const String& text, const Vec2& position, const Color& color) {
drawText(font, text, position.x, position.y, color);
}
void GLRenderer::drawText(const FontAtlas& font, const String& text, float x, float y, const Color& color) {
float cursorX = x;
float cursorY = y;
float baselineY = cursorY + font.getAscent();
for (char32_t codepoint : text.toUtf32()) {
if (codepoint == '\n') {
cursorX = x;
cursorY += font.getLineHeight();
baselineY = cursorY + font.getAscent();
continue;
}
const Glyph* glyph = font.getGlyph(codepoint);
if (glyph) {
float penX = cursorX;
cursorX += glyph->advance;
if (glyph->width <= 0.0f || glyph->height <= 0.0f) {
continue;
}
float xPos = penX + glyph->bearingX;
float yPos = baselineY + glyph->bearingY;
Rect destRect(xPos, yPos, glyph->width, glyph->height);
GLSpriteBatch::SpriteData data;
data.position = glm::vec2(destRect.origin.x, destRect.origin.y);
data.size = glm::vec2(destRect.size.width, destRect.size.height);
data.texCoordMin = glm::vec2(glyph->u0, glyph->v0);
data.texCoordMax = glm::vec2(glyph->u1, glyph->v1);
data.color = glm::vec4(color.r, color.g, color.b, color.a);
data.rotation = 0.0f;
data.anchor = glm::vec2(0.0f, 0.0f);
data.isSDF = font.isSDF();
spriteBatch_.draw(*font.getTexture(), data);
}
}
}
void GLRenderer::resetStats() {
stats_ = Stats{};
}
void GLRenderer::initShapeRendering() {
// 编译形状着色器
shapeShader_.compileFromSource(SHAPE_VERTEX_SHADER, SHAPE_FRAGMENT_SHADER);
// 创建 VAO 和 VBO
glGenVertexArrays(1, &shapeVao_);
glGenBuffers(1, &shapeVbo_);
glBindVertexArray(shapeVao_);
glBindBuffer(GL_ARRAY_BUFFER, shapeVbo_);
glBufferData(GL_ARRAY_BUFFER, SHAPE_VBO_SIZE, nullptr, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), nullptr);
glBindVertexArray(0);
// VRAM 跟踪
VRAMManager::getInstance().allocBuffer(SHAPE_VBO_SIZE);
}
} // namespace easy2d

View File

@ -1,129 +0,0 @@
#include <easy2d/graphics/opengl/gl_shader.h>
#include <easy2d/utils/logger.h>
#include <fstream>
#include <sstream>
namespace easy2d {
GLShader::GLShader() : programID_(0) {
}
GLShader::~GLShader() {
if (programID_ != 0) {
glDeleteProgram(programID_);
}
}
bool GLShader::compileFromSource(const char* vertexSource, const char* fragmentSource) {
GLuint vertexShader = compileShader(GL_VERTEX_SHADER, vertexSource);
if (vertexShader == 0) return false;
GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentSource);
if (fragmentShader == 0) {
glDeleteShader(vertexShader);
return false;
}
programID_ = glCreateProgram();
glAttachShader(programID_, vertexShader);
glAttachShader(programID_, fragmentShader);
glLinkProgram(programID_);
GLint success;
glGetProgramiv(programID_, GL_LINK_STATUS, &success);
if (!success) {
char infoLog[512];
glGetProgramInfoLog(programID_, 512, nullptr, infoLog);
E2D_LOG_ERROR("Shader program linking failed: {}", infoLog);
glDeleteProgram(programID_);
programID_ = 0;
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
return success == GL_TRUE;
}
bool GLShader::compileFromFile(const std::string& vertexPath, const std::string& fragmentPath) {
std::ifstream vShaderFile(vertexPath);
std::ifstream fShaderFile(fragmentPath);
if (!vShaderFile.is_open() || !fShaderFile.is_open()) {
E2D_LOG_ERROR("Failed to open shader files: {}, {}", vertexPath, fragmentPath);
return false;
}
std::stringstream vShaderStream, fShaderStream;
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
return compileFromSource(vShaderStream.str().c_str(), fShaderStream.str().c_str());
}
void GLShader::bind() const {
glUseProgram(programID_);
}
void GLShader::unbind() const {
glUseProgram(0);
}
void GLShader::setBool(const std::string& name, bool value) {
glUniform1i(getUniformLocation(name), value ? 1 : 0);
}
void GLShader::setInt(const std::string& name, int value) {
glUniform1i(getUniformLocation(name), value);
}
void GLShader::setFloat(const std::string& name, float value) {
glUniform1f(getUniformLocation(name), value);
}
void GLShader::setVec2(const std::string& name, const glm::vec2& value) {
glUniform2fv(getUniformLocation(name), 1, &value[0]);
}
void GLShader::setVec3(const std::string& name, const glm::vec3& value) {
glUniform3fv(getUniformLocation(name), 1, &value[0]);
}
void GLShader::setVec4(const std::string& name, const glm::vec4& value) {
glUniform4fv(getUniformLocation(name), 1, &value[0]);
}
void GLShader::setMat4(const std::string& name, const glm::mat4& value) {
glUniformMatrix4fv(getUniformLocation(name), 1, GL_FALSE, &value[0][0]);
}
GLuint GLShader::compileShader(GLenum type, const char* source) {
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
GLint success;
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
char infoLog[512];
glGetShaderInfoLog(shader, 512, nullptr, infoLog);
E2D_LOG_ERROR("Shader compilation failed: {}", infoLog);
glDeleteShader(shader);
return 0;
}
return shader;
}
GLint GLShader::getUniformLocation(const std::string& name) {
auto it = uniformCache_.find(name);
if (it != uniformCache_.end()) {
return it->second;
}
GLint location = glGetUniformLocation(programID_, name.c_str());
uniformCache_[name] = location;
return location;
}
} // namespace easy2d

View File

@ -1,216 +0,0 @@
#include <easy2d/graphics/opengl/gl_sprite_batch.h>
#include <easy2d/utils/logger.h>
#include <glm/gtc/matrix_transform.hpp>
#include <cstring>
namespace easy2d {
// 顶点着色器 (GLES 3.2)
static const char* SPRITE_VERTEX_SHADER = R"(
#version 300 es
precision highp float;
layout(location = 0) in vec2 aPosition;
layout(location = 1) in vec2 aTexCoord;
layout(location = 2) in vec4 aColor;
uniform mat4 uViewProjection;
out vec2 vTexCoord;
out vec4 vColor;
void main() {
gl_Position = uViewProjection * vec4(aPosition, 0.0, 1.0);
vTexCoord = aTexCoord;
vColor = aColor;
}
)";
// 片段着色器 (GLES 3.2)
static const char* SPRITE_FRAGMENT_SHADER = R"(
#version 300 es
precision highp float;
in vec2 vTexCoord;
in vec4 vColor;
uniform sampler2D uTexture;
uniform int uUseSDF;
uniform float uSdfOnEdge;
uniform float uSdfScale;
out vec4 fragColor;
void main() {
if (uUseSDF == 1) {
float dist = texture(uTexture, vTexCoord).r;
float sd = (dist - uSdfOnEdge) * uSdfScale;
float w = fwidth(sd);
float alpha = smoothstep(-w, w, sd);
fragColor = vec4(vColor.rgb, vColor.a * alpha);
} else {
fragColor = texture(uTexture, vTexCoord) * vColor;
}
}
)";
GLSpriteBatch::GLSpriteBatch()
: vao_(0), vbo_(0), ibo_(0), currentTexture_(nullptr), currentIsSDF_(false), drawCallCount_(0), spriteCount_(0) {
vertices_.reserve(MAX_SPRITES * VERTICES_PER_SPRITE);
indices_.reserve(MAX_SPRITES * INDICES_PER_SPRITE);
}
GLSpriteBatch::~GLSpriteBatch() {
shutdown();
}
bool GLSpriteBatch::init() {
// 创建并编译着色器
if (!shader_.compileFromSource(SPRITE_VERTEX_SHADER, SPRITE_FRAGMENT_SHADER)) {
E2D_LOG_ERROR("Failed to compile sprite batch shader");
return false;
}
// 生成 VAO、VBO、IBO
glGenVertexArrays(1, &vao_);
glGenBuffers(1, &vbo_);
glGenBuffers(1, &ibo_);
glBindVertexArray(vao_);
// 设置 VBO
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
glBufferData(GL_ARRAY_BUFFER, MAX_SPRITES * VERTICES_PER_SPRITE * sizeof(Vertex), nullptr, GL_DYNAMIC_DRAW);
// 设置顶点属性
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, position));
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, texCoord));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, color));
// 生成索引缓冲区
std::vector<GLuint> indices;
indices.reserve(MAX_SPRITES * INDICES_PER_SPRITE);
for (size_t i = 0; i < MAX_SPRITES; ++i) {
GLuint base = static_cast<GLuint>(i * VERTICES_PER_SPRITE);
indices.push_back(base + 0);
indices.push_back(base + 1);
indices.push_back(base + 2);
indices.push_back(base + 0);
indices.push_back(base + 2);
indices.push_back(base + 3);
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(GLuint), indices.data(), GL_STATIC_DRAW);
glBindVertexArray(0);
return true;
}
void GLSpriteBatch::shutdown() {
if (vao_ != 0) {
glDeleteVertexArrays(1, &vao_);
vao_ = 0;
}
if (vbo_ != 0) {
glDeleteBuffers(1, &vbo_);
vbo_ = 0;
}
if (ibo_ != 0) {
glDeleteBuffers(1, &ibo_);
ibo_ = 0;
}
}
void GLSpriteBatch::begin(const glm::mat4& viewProjection) {
viewProjection_ = viewProjection;
vertices_.clear();
currentTexture_ = nullptr;
currentIsSDF_ = false;
drawCallCount_ = 0;
spriteCount_ = 0;
}
void GLSpriteBatch::draw(const Texture& texture, const SpriteData& data) {
// 如果纹理改变或缓冲区已满,先 flush
if (currentTexture_ != nullptr && (currentTexture_ != &texture || currentIsSDF_ != data.isSDF || vertices_.size() >= MAX_SPRITES * VERTICES_PER_SPRITE)) {
flush();
}
currentTexture_ = &texture;
currentIsSDF_ = data.isSDF;
// 计算变换后的顶点位置
glm::vec2 anchorOffset(data.size.x * data.anchor.x, data.size.y * data.anchor.y);
float cosR = cosf(data.rotation);
float sinR = sinf(data.rotation);
auto transform = [&](float x, float y) -> glm::vec2 {
float rx = x - anchorOffset.x;
float ry = y - anchorOffset.y;
return glm::vec2(
data.position.x + rx * cosR - ry * sinR,
data.position.y + rx * sinR + ry * cosR
);
};
glm::vec4 color(data.color.r, data.color.g, data.color.b, data.color.a);
// 添加四个顶点(图片已在加载时翻转,纹理坐标直接使用)
// v0(左上) -- v1(右上)
// | |
// v3(左下) -- v2(右下)
Vertex v0{ transform(0, 0), glm::vec2(data.texCoordMin.x, data.texCoordMin.y), color };
Vertex v1{ transform(data.size.x, 0), glm::vec2(data.texCoordMax.x, data.texCoordMin.y), color };
Vertex v2{ transform(data.size.x, data.size.y), glm::vec2(data.texCoordMax.x, data.texCoordMax.y), color };
Vertex v3{ transform(0, data.size.y), glm::vec2(data.texCoordMin.x, data.texCoordMax.y), color };
vertices_.push_back(v0);
vertices_.push_back(v1);
vertices_.push_back(v2);
vertices_.push_back(v3);
spriteCount_++;
}
void GLSpriteBatch::end() {
if (!vertices_.empty()) {
flush();
}
}
void GLSpriteBatch::flush() {
if (vertices_.empty() || currentTexture_ == nullptr) return;
// 绑定纹理
GLuint texID = static_cast<GLuint>(reinterpret_cast<uintptr_t>(currentTexture_->getNativeHandle()));
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texID);
// 使用着色器
shader_.bind();
shader_.setMat4("uViewProjection", viewProjection_);
shader_.setInt("uTexture", 0);
shader_.setInt("uUseSDF", currentIsSDF_ ? 1 : 0);
shader_.setFloat("uSdfOnEdge", 128.0f / 255.0f);
shader_.setFloat("uSdfScale", 255.0f / 64.0f);
// 更新 VBO 数据
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
glBufferSubData(GL_ARRAY_BUFFER, 0, vertices_.size() * sizeof(Vertex), vertices_.data());
// 绘制
glBindVertexArray(vao_);
GLsizei indexCount = static_cast<GLsizei>(vertices_.size() / VERTICES_PER_SPRITE * INDICES_PER_SPRITE);
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, nullptr);
drawCallCount_++;
vertices_.clear();
}
} // namespace easy2d

View File

@ -1,446 +0,0 @@
#include <easy2d/graphics/opengl/gl_texture.h>
#include <easy2d/graphics/vram_manager.h>
#define STB_IMAGE_IMPLEMENTATION
#include <stb/stb_image.h>
#include <easy2d/utils/logger.h>
#include <cstring>
#include <fstream>
namespace easy2d {
// ============================================================================
// KTX 文件头结构
// ============================================================================
#pragma pack(push, 1)
struct KTXHeader {
uint8_t identifier[12];
uint32_t endianness;
uint32_t glType;
uint32_t glTypeSize;
uint32_t glFormat;
uint32_t glInternalFormat;
uint32_t glBaseInternalFormat;
uint32_t pixelWidth;
uint32_t pixelHeight;
uint32_t pixelDepth;
uint32_t numberOfArrayElements;
uint32_t numberOfFaces;
uint32_t numberOfMipmapLevels;
uint32_t bytesOfKeyValueData;
};
#pragma pack(pop)
// KTX 文件标识符
static const uint8_t KTX_IDENTIFIER[12] = {
0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31,
0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A
};
// ============================================================================
// DDS 文件头结构
// ============================================================================
#pragma pack(push, 1)
struct DDSPixelFormat {
uint32_t size;
uint32_t flags;
uint32_t fourCC;
uint32_t rgbBitCount;
uint32_t rBitMask;
uint32_t gBitMask;
uint32_t bBitMask;
uint32_t aBitMask;
};
struct DDSHeader {
uint32_t magic;
uint32_t size;
uint32_t flags;
uint32_t height;
uint32_t width;
uint32_t pitchOrLinearSize;
uint32_t depth;
uint32_t mipMapCount;
uint32_t reserved1[11];
DDSPixelFormat pixelFormat;
uint32_t caps;
uint32_t caps2;
uint32_t caps3;
uint32_t caps4;
uint32_t reserved2;
};
struct DDSHeaderDXT10 {
uint32_t dxgiFormat;
uint32_t resourceDimension;
uint32_t miscFlag;
uint32_t arraySize;
uint32_t miscFlags2;
};
#pragma pack(pop)
static constexpr uint32_t DDS_MAGIC = 0x20534444; // "DDS "
static constexpr uint32_t DDPF_FOURCC = 0x04;
static uint32_t makeFourCC(char a, char b, char c, char d) {
return static_cast<uint32_t>(a) | (static_cast<uint32_t>(b) << 8) |
(static_cast<uint32_t>(c) << 16) | (static_cast<uint32_t>(d) << 24);
}
// ============================================================================
// GLTexture 实现
// ============================================================================
GLTexture::GLTexture(int width, int height, const uint8_t* pixels, int channels)
: textureID_(0), width_(width), height_(height), channels_(channels),
format_(PixelFormat::RGBA8), dataSize_(0) {
// 保存像素数据用于生成遮罩
if (pixels) {
pixelData_.resize(width * height * channels);
std::memcpy(pixelData_.data(), pixels, pixelData_.size());
}
createTexture(pixels);
}
GLTexture::GLTexture(const std::string& filepath)
: textureID_(0), width_(0), height_(0), channels_(0),
format_(PixelFormat::RGBA8), dataSize_(0) {
// 检查是否为压缩纹理格式
std::string ext = filepath.substr(filepath.find_last_of('.') + 1);
if (ext == "ktx" || ext == "KTX") {
loadCompressed(filepath);
return;
}
if (ext == "dds" || ext == "DDS") {
loadCompressed(filepath);
return;
}
// 不翻转图片,保持原始方向
stbi_set_flip_vertically_on_load(false);
uint8_t* data = stbi_load(filepath.c_str(), &width_, &height_, &channels_, 0);
if (data) {
// 保存像素数据用于生成遮罩
pixelData_.resize(width_ * height_ * channels_);
std::memcpy(pixelData_.data(), data, pixelData_.size());
createTexture(data);
stbi_image_free(data);
} else {
E2D_LOG_ERROR("Failed to load texture: {}", filepath);
}
}
GLTexture::~GLTexture() {
if (textureID_ != 0) {
glDeleteTextures(1, &textureID_);
// VRAM 跟踪: 释放纹理显存
if (dataSize_ > 0) {
VRAMManager::getInstance().freeTexture(dataSize_);
}
}
}
void GLTexture::setFilter(bool linear) {
bind();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, linear ? GL_LINEAR : GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, linear ? GL_LINEAR : GL_NEAREST);
}
void GLTexture::setWrap(bool repeat) {
bind();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, repeat ? GL_REPEAT : GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, repeat ? GL_REPEAT : GL_CLAMP_TO_EDGE);
}
void GLTexture::bind(unsigned int slot) const {
glActiveTexture(GL_TEXTURE0 + slot);
glBindTexture(GL_TEXTURE_2D, textureID_);
}
void GLTexture::unbind() const {
glBindTexture(GL_TEXTURE_2D, 0);
}
void GLTexture::createTexture(const uint8_t* pixels) {
GLenum format = GL_RGBA;
GLenum internalFormat = GL_RGBA8;
int unpackAlignment = 4;
if (channels_ == 1) {
format = GL_RED;
internalFormat = GL_R8;
unpackAlignment = 1;
format_ = PixelFormat::R8;
} else if (channels_ == 3) {
format = GL_RGB;
internalFormat = GL_RGB8;
unpackAlignment = 1;
format_ = PixelFormat::RGB8;
} else if (channels_ == 4) {
format = GL_RGBA;
internalFormat = GL_RGBA8;
unpackAlignment = 4;
format_ = PixelFormat::RGBA8;
}
glGenTextures(1, &textureID_);
bind();
GLint prevUnpackAlignment = 4;
glGetIntegerv(GL_UNPACK_ALIGNMENT, &prevUnpackAlignment);
glPixelStorei(GL_UNPACK_ALIGNMENT, unpackAlignment);
glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width_, height_, 0, format, GL_UNSIGNED_BYTE, pixels);
glPixelStorei(GL_UNPACK_ALIGNMENT, prevUnpackAlignment);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// 使用 NEAREST 过滤器,更适合像素艺术风格的精灵
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glGenerateMipmap(GL_TEXTURE_2D);
// VRAM 跟踪
dataSize_ = static_cast<size_t>(width_ * height_ * channels_);
VRAMManager::getInstance().allocTexture(dataSize_);
}
// ============================================================================
// 压缩纹理加载
// ============================================================================
bool GLTexture::loadCompressed(const std::string& filepath) {
std::string ext = filepath.substr(filepath.find_last_of('.') + 1);
if (ext == "ktx" || ext == "KTX") {
return loadKTX(filepath);
}
if (ext == "dds" || ext == "DDS") {
return loadDDS(filepath);
}
E2D_LOG_ERROR("Unsupported compressed texture format: {}", filepath);
return false;
}
bool GLTexture::loadKTX(const std::string& filepath) {
std::ifstream file(filepath, std::ios::binary);
if (!file.is_open()) {
E2D_LOG_ERROR("Failed to open KTX file: {}", filepath);
return false;
}
KTXHeader header;
file.read(reinterpret_cast<char*>(&header), sizeof(header));
if (!file) {
E2D_LOG_ERROR("Failed to read KTX header: {}", filepath);
return false;
}
// 验证标识符
if (std::memcmp(header.identifier, KTX_IDENTIFIER, 12) != 0) {
E2D_LOG_ERROR("Invalid KTX identifier: {}", filepath);
return false;
}
width_ = static_cast<int>(header.pixelWidth);
height_ = static_cast<int>(header.pixelHeight);
channels_ = 4; // 压缩纹理通常解压为 RGBA
// 确定压缩格式
GLenum glInternalFormat = header.glInternalFormat;
switch (glInternalFormat) {
case GL_COMPRESSED_RGB8_ETC2:
format_ = PixelFormat::ETC2_RGB8;
channels_ = 3;
break;
case GL_COMPRESSED_RGBA8_ETC2_EAC:
format_ = PixelFormat::ETC2_RGBA8;
break;
case GL_COMPRESSED_RGBA_ASTC_4x4:
format_ = PixelFormat::ASTC_4x4;
break;
case GL_COMPRESSED_RGBA_ASTC_6x6:
format_ = PixelFormat::ASTC_6x6;
break;
case GL_COMPRESSED_RGBA_ASTC_8x8:
format_ = PixelFormat::ASTC_8x8;
break;
default:
E2D_LOG_ERROR("Unsupported KTX internal format: {:#06x}", glInternalFormat);
return false;
}
// 跳过 key-value 数据
file.seekg(header.bytesOfKeyValueData, std::ios::cur);
// 读取第一个 mipmap level
uint32_t imageSize = 0;
file.read(reinterpret_cast<char*>(&imageSize), sizeof(imageSize));
if (!file || imageSize == 0) {
E2D_LOG_ERROR("Failed to read KTX image size: {}", filepath);
return false;
}
std::vector<uint8_t> compressedData(imageSize);
file.read(reinterpret_cast<char*>(compressedData.data()), imageSize);
if (!file) {
E2D_LOG_ERROR("Failed to read KTX image data: {}", filepath);
return false;
}
// 创建 GL 纹理
glGenTextures(1, &textureID_);
bind();
glCompressedTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, width_, height_,
0, static_cast<GLsizei>(imageSize),
compressedData.data());
GLenum err = glGetError();
if (err != GL_NO_ERROR) {
E2D_LOG_ERROR("glCompressedTexImage2D failed for KTX: {:#06x}", err);
glDeleteTextures(1, &textureID_);
textureID_ = 0;
return false;
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// VRAM 跟踪
dataSize_ = imageSize;
VRAMManager::getInstance().allocTexture(dataSize_);
E2D_LOG_INFO("Loaded compressed KTX texture: {} ({}x{}, format={:#06x})",
filepath, width_, height_, glInternalFormat);
return true;
}
bool GLTexture::loadDDS(const std::string& filepath) {
std::ifstream file(filepath, std::ios::binary);
if (!file.is_open()) {
E2D_LOG_ERROR("Failed to open DDS file: {}", filepath);
return false;
}
DDSHeader header;
file.read(reinterpret_cast<char*>(&header), sizeof(header));
if (!file) {
E2D_LOG_ERROR("Failed to read DDS header: {}", filepath);
return false;
}
if (header.magic != DDS_MAGIC) {
E2D_LOG_ERROR("Invalid DDS magic: {}", filepath);
return false;
}
width_ = static_cast<int>(header.width);
height_ = static_cast<int>(header.height);
channels_ = 4;
GLenum glInternalFormat = 0;
// 检查 DX10 扩展头
if ((header.pixelFormat.flags & DDPF_FOURCC) &&
header.pixelFormat.fourCC == makeFourCC('D', 'X', '1', '0')) {
DDSHeaderDXT10 dx10Header;
file.read(reinterpret_cast<char*>(&dx10Header), sizeof(dx10Header));
if (!file) {
E2D_LOG_ERROR("Failed to read DDS DX10 header: {}", filepath);
return false;
}
// DXGI_FORMAT 映射到 GL 格式
switch (dx10Header.dxgiFormat) {
case 147: // DXGI_FORMAT_ETC2_RGB8
glInternalFormat = GL_COMPRESSED_RGB8_ETC2;
format_ = PixelFormat::ETC2_RGB8;
channels_ = 3;
break;
case 148: // DXGI_FORMAT_ETC2_RGBA8
glInternalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC;
format_ = PixelFormat::ETC2_RGBA8;
break;
default:
E2D_LOG_ERROR("Unsupported DDS DX10 format: {}", dx10Header.dxgiFormat);
return false;
}
} else {
E2D_LOG_ERROR("DDS file does not use DX10 extension, unsupported: {}", filepath);
return false;
}
// 计算压缩数据大小
size_t blockSize = (glInternalFormat == GL_COMPRESSED_RGB8_ETC2) ? 8 : 16;
size_t blocksWide = (width_ + 3) / 4;
size_t blocksHigh = (height_ + 3) / 4;
size_t imageSize = blocksWide * blocksHigh * blockSize;
std::vector<uint8_t> compressedData(imageSize);
file.read(reinterpret_cast<char*>(compressedData.data()), imageSize);
if (!file) {
E2D_LOG_ERROR("Failed to read DDS image data: {}", filepath);
return false;
}
// 创建 GL 纹理
glGenTextures(1, &textureID_);
bind();
glCompressedTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, width_, height_,
0, static_cast<GLsizei>(imageSize),
compressedData.data());
GLenum err = glGetError();
if (err != GL_NO_ERROR) {
E2D_LOG_ERROR("glCompressedTexImage2D failed for DDS: {:#06x}", err);
glDeleteTextures(1, &textureID_);
textureID_ = 0;
return false;
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// VRAM 跟踪
dataSize_ = imageSize;
VRAMManager::getInstance().allocTexture(dataSize_);
E2D_LOG_INFO("Loaded compressed DDS texture: {} ({}x{})", filepath, width_, height_);
return true;
}
void GLTexture::generateAlphaMask() {
if (pixelData_.empty() || width_ <= 0 || height_ <= 0) {
E2D_LOG_WARN("Cannot generate alpha mask: no pixel data available");
return;
}
alphaMask_ = std::make_unique<AlphaMask>(
AlphaMask::createFromPixels(pixelData_.data(), width_, height_, channels_)
);
E2D_LOG_DEBUG("Generated alpha mask for texture: {}x{}", width_, height_);
}
PixelFormat GLTexture::getFormat() const {
return format_;
}
Ptr<Texture> GLTexture::create(int width, int height, PixelFormat format) {
int channels = 4;
switch (format) {
case PixelFormat::R8: channels = 1; break;
case PixelFormat::RG8: channels = 2; break;
case PixelFormat::RGB8: channels = 3; break;
case PixelFormat::RGBA8: channels = 4; break;
default: channels = 4; break;
}
return makePtr<GLTexture>(width, height, nullptr, channels);
}
} // namespace easy2d

View File

@ -1,15 +0,0 @@
#include <easy2d/graphics/render_backend.h>
#include <easy2d/graphics/opengl/gl_renderer.h>
namespace easy2d {
UniquePtr<RenderBackend> RenderBackend::create(BackendType type) {
switch (type) {
case BackendType::OpenGL:
return makeUnique<GLRenderer>();
default:
return nullptr;
}
}
} // namespace easy2d

View File

@ -1,222 +0,0 @@
#include <easy2d/graphics/shader_preset.h>
#include <easy2d/utils/logger.h>
namespace easy2d {
// ============================================================================
// ShaderPreset实现
// ============================================================================
Ptr<GLShader> ShaderPreset::Water(const WaterParams& params) {
auto shader = std::make_shared<GLShader>();
if (!shader->compileFromSource(ShaderSource::StandardVert, ShaderSource::WaterFrag)) {
E2D_ERROR("编译水波纹Shader失败");
return nullptr;
}
// 设置默认参数
shader->setFloat("u_waveSpeed", params.waveSpeed);
shader->setFloat("u_waveAmplitude", params.waveAmplitude);
shader->setFloat("u_waveFrequency", params.waveFrequency);
E2D_INFO("创建水波纹Shader预设");
return shader;
}
Ptr<GLShader> ShaderPreset::Outline(const OutlineParams& params) {
auto shader = std::make_shared<GLShader>();
if (!shader->compileFromSource(ShaderSource::StandardVert, ShaderSource::OutlineFrag)) {
E2D_ERROR("编译描边Shader失败");
return nullptr;
}
// 设置默认参数
shader->setVec4("u_outlineColor", glm::vec4(params.color.r, params.color.g,
params.color.b, params.color.a));
shader->setFloat("u_thickness", params.thickness);
E2D_INFO("创建描边Shader预设");
return shader;
}
Ptr<GLShader> ShaderPreset::Distortion(const DistortionParams& params) {
auto shader = std::make_shared<GLShader>();
if (!shader->compileFromSource(ShaderSource::StandardVert, ShaderSource::DistortionFrag)) {
E2D_ERROR("编译扭曲Shader失败");
return nullptr;
}
// 设置默认参数
shader->setFloat("u_distortionAmount", params.distortionAmount);
shader->setFloat("u_timeScale", params.timeScale);
E2D_INFO("创建扭曲Shader预设");
return shader;
}
Ptr<GLShader> ShaderPreset::Pixelate(const PixelateParams& params) {
auto shader = std::make_shared<GLShader>();
if (!shader->compileFromSource(ShaderSource::StandardVert, ShaderSource::PixelateFrag)) {
E2D_ERROR("编译像素化Shader失败");
return nullptr;
}
// 设置默认参数
shader->setFloat("u_pixelSize", params.pixelSize);
E2D_INFO("创建像素化Shader预设");
return shader;
}
Ptr<GLShader> ShaderPreset::Invert(const InvertParams& params) {
auto shader = std::make_shared<GLShader>();
if (!shader->compileFromSource(ShaderSource::StandardVert, ShaderSource::InvertFrag)) {
E2D_ERROR("编译反相Shader失败");
return nullptr;
}
// 设置默认参数
shader->setFloat("u_strength", params.strength);
E2D_INFO("创建反相Shader预设");
return shader;
}
Ptr<GLShader> ShaderPreset::Grayscale(const GrayscaleParams& params) {
auto shader = std::make_shared<GLShader>();
if (!shader->compileFromSource(ShaderSource::StandardVert, ShaderSource::GrayscaleFrag)) {
E2D_ERROR("编译灰度Shader失败");
return nullptr;
}
// 设置默认参数
shader->setFloat("u_intensity", params.intensity);
E2D_INFO("创建灰度Shader预设");
return shader;
}
Ptr<GLShader> ShaderPreset::Blur(const BlurParams& params) {
auto shader = std::make_shared<GLShader>();
if (!shader->compileFromSource(ShaderSource::StandardVert, ShaderSource::BlurFrag)) {
E2D_ERROR("编译模糊Shader失败");
return nullptr;
}
// 设置默认参数
shader->setFloat("u_radius", params.radius);
E2D_INFO("创建模糊Shader预设");
return shader;
}
Ptr<GLShader> ShaderPreset::GrayscaleOutline(const GrayscaleParams& grayParams,
const OutlineParams& outlineParams) {
// 创建组合效果的片段着色器 (GLES 3.2)
const char* combinedFrag = R"(
#version 300 es
precision highp float;
in vec2 v_texCoord;
uniform sampler2D u_texture;
uniform float u_grayIntensity;
uniform vec4 u_outlineColor;
uniform float u_thickness;
uniform vec2 u_textureSize;
out vec4 fragColor;
void main() {
vec4 color = texture(u_texture, v_texCoord);
// 灰度效果
float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
color.rgb = mix(color.rgb, vec3(gray), u_grayIntensity);
// 描边效果
float alpha = 0.0;
vec2 offset = u_thickness / u_textureSize;
alpha += texture(u_texture, v_texCoord + vec2(-offset.x, 0.0)).a;
alpha += texture(u_texture, v_texCoord + vec2(offset.x, 0.0)).a;
alpha += texture(u_texture, v_texCoord + vec2(0.0, -offset.y)).a;
alpha += texture(u_texture, v_texCoord + vec2(0.0, offset.y)).a;
if (color.a < 0.1 && alpha > 0.0) {
fragColor = u_outlineColor;
} else {
fragColor = color;
}
}
)";
auto shader = std::make_shared<GLShader>();
if (!shader->compileFromSource(ShaderSource::StandardVert, combinedFrag)) {
E2D_ERROR("编译灰度+描边Shader失败");
return nullptr;
}
// 设置默认参数
shader->setFloat("u_grayIntensity", grayParams.intensity);
shader->setVec4("u_outlineColor", glm::vec4(outlineParams.color.r, outlineParams.color.g,
outlineParams.color.b, outlineParams.color.a));
shader->setFloat("u_thickness", outlineParams.thickness);
E2D_INFO("创建灰度+描边组合Shader预设");
return shader;
}
Ptr<GLShader> ShaderPreset::PixelateInvert(const PixelateParams& pixParams,
const InvertParams& invParams) {
// 创建组合效果的片段着色器 (GLES 3.2)
const char* combinedFrag = R"(
#version 300 es
precision highp float;
in vec2 v_texCoord;
uniform sampler2D u_texture;
uniform float u_pixelSize;
uniform vec2 u_textureSize;
uniform float u_invertStrength;
out vec4 fragColor;
void main() {
// 像素化
vec2 pixel = u_pixelSize / u_textureSize;
vec2 uv = floor(v_texCoord / pixel) * pixel + pixel * 0.5;
vec4 color = texture(u_texture, uv);
// 反相
vec3 inverted = 1.0 - color.rgb;
color.rgb = mix(color.rgb, inverted, u_invertStrength);
fragColor = color;
}
)";
auto shader = std::make_shared<GLShader>();
if (!shader->compileFromSource(ShaderSource::StandardVert, combinedFrag)) {
E2D_ERROR("编译像素化+反相Shader失败");
return nullptr;
}
// 设置默认参数
shader->setFloat("u_pixelSize", pixParams.pixelSize);
shader->setFloat("u_invertStrength", invParams.strength);
E2D_INFO("创建像素化+反相组合Shader预设");
return shader;
}
} // namespace easy2d

View File

@ -1,483 +0,0 @@
#include <easy2d/graphics/shader_system.h>
#include <easy2d/core/color.h>
#include <easy2d/utils/logger.h>
#include <fstream>
#include <sstream>
#include <sys/stat.h>
#ifdef _WIN32
#include <windows.h>
#endif
namespace easy2d {
// ============================================================================
// 内置Shader源码
// ============================================================================
// 标准精灵着色器 (GLES 3.2)
static const char* BUILTIN_SPRITE_VERT = R"(
#version 300 es
precision highp float;
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texCoord;
layout(location = 2) in vec4 a_color;
uniform mat4 u_viewProjection;
uniform mat4 u_model;
out vec2 v_texCoord;
out vec4 v_color;
void main() {
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
v_color = a_color;
}
)";
static const char* BUILTIN_SPRITE_FRAG = R"(
#version 300 es
precision highp float;
in vec2 v_texCoord;
in vec4 v_color;
uniform sampler2D u_texture;
uniform float u_opacity;
out vec4 fragColor;
void main() {
vec4 texColor = texture(u_texture, v_texCoord);
fragColor = texColor * v_color;
fragColor.a *= u_opacity;
if (fragColor.a < 0.01) {
discard;
}
}
)";
// 粒子着色器 (GLES 3.2)
static const char* BUILTIN_PARTICLE_VERT = R"(
#version 300 es
precision highp float;
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texCoord;
layout(location = 2) in vec4 a_color;
layout(location = 3) in float a_size;
layout(location = 4) in float a_rotation;
uniform mat4 u_viewProjection;
out vec2 v_texCoord;
out vec4 v_color;
void main() {
float c = cos(a_rotation);
float s = sin(a_rotation);
mat2 rot = mat2(c, -s, s, c);
vec2 pos = rot * a_position * a_size;
gl_Position = u_viewProjection * vec4(pos, 0.0, 1.0);
v_texCoord = a_texCoord;
v_color = a_color;
}
)";
static const char* BUILTIN_PARTICLE_FRAG = R"(
#version 300 es
precision highp float;
in vec2 v_texCoord;
in vec4 v_color;
uniform sampler2D u_texture;
uniform int u_textureEnabled;
out vec4 fragColor;
void main() {
vec4 color = v_color;
if (u_textureEnabled > 0) {
color *= texture(u_texture, v_texCoord);
}
// 圆形粒子
vec2 center = v_texCoord - vec2(0.5);
float dist = length(center);
float alpha = 1.0 - smoothstep(0.4, 0.5, dist);
color.a *= alpha;
if (color.a < 0.01) {
discard;
}
fragColor = color;
}
)";
// 后处理着色器 (GLES 3.2)
static const char* BUILTIN_POSTPROCESS_VERT = R"(
#version 300 es
precision highp float;
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texCoord;
out vec2 v_texCoord;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
}
)";
static const char* BUILTIN_POSTPROCESS_FRAG = R"(
#version 300 es
precision highp float;
in vec2 v_texCoord;
uniform sampler2D u_texture;
uniform vec2 u_resolution;
uniform float u_time;
out vec4 fragColor;
void main() {
fragColor = texture(u_texture, v_texCoord);
}
)";
// 形状渲染着色器 (GLES 3.2)
static const char* BUILTIN_SHAPE_VERT = R"(
#version 300 es
precision highp float;
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec4 a_color;
uniform mat4 u_viewProjection;
out vec4 v_color;
void main() {
gl_Position = u_viewProjection * vec4(a_position, 0.0, 1.0);
v_color = a_color;
}
)";
static const char* BUILTIN_SHAPE_FRAG = R"(
#version 300 es
precision highp float;
in vec4 v_color;
out vec4 fragColor;
void main() {
fragColor = v_color;
}
)";
// ============================================================================
// ShaderSystem实现
// ============================================================================
ShaderSystem& ShaderSystem::getInstance() {
static ShaderSystem instance;
return instance;
}
bool ShaderSystem::init() {
E2D_INFO("初始化Shader系统...");
if (!loadBuiltinShaders()) {
E2D_ERROR("加载内置Shader失败");
return false;
}
E2D_INFO("Shader系统初始化完成");
return true;
}
void ShaderSystem::shutdown() {
E2D_INFO("关闭Shader系统...");
clear();
builtinSpriteShader_.reset();
builtinParticleShader_.reset();
builtinPostProcessShader_.reset();
builtinShapeShader_.reset();
}
bool ShaderSystem::loadBuiltinShaders() {
// 加载精灵Shader
builtinSpriteShader_ = std::make_shared<GLShader>();
if (!builtinSpriteShader_->compileFromSource(BUILTIN_SPRITE_VERT, BUILTIN_SPRITE_FRAG)) {
E2D_ERROR("编译内置精灵Shader失败");
return false;
}
// 加载粒子Shader
builtinParticleShader_ = std::make_shared<GLShader>();
if (!builtinParticleShader_->compileFromSource(BUILTIN_PARTICLE_VERT, BUILTIN_PARTICLE_FRAG)) {
E2D_ERROR("编译内置粒子Shader失败");
return false;
}
// 加载后处理Shader
builtinPostProcessShader_ = std::make_shared<GLShader>();
if (!builtinPostProcessShader_->compileFromSource(BUILTIN_POSTPROCESS_VERT, BUILTIN_POSTPROCESS_FRAG)) {
E2D_ERROR("编译内置后处理Shader失败");
return false;
}
// 加载形状Shader
builtinShapeShader_ = std::make_shared<GLShader>();
if (!builtinShapeShader_->compileFromSource(BUILTIN_SHAPE_VERT, BUILTIN_SHAPE_FRAG)) {
E2D_ERROR("编译内置形状Shader失败");
return false;
}
E2D_INFO("内置Shader加载成功");
return true;
}
Ptr<GLShader> ShaderSystem::loadFromFile(const std::string& name,
const std::string& vertPath,
const std::string& fragPath) {
// 读取文件内容
std::string vertSource = readFile(vertPath);
std::string fragSource = readFile(fragPath);
if (vertSource.empty()) {
E2D_ERROR("无法读取顶点着色器文件: {}", vertPath);
return nullptr;
}
if (fragSource.empty()) {
E2D_ERROR("无法读取片段着色器文件: {}", fragPath);
return nullptr;
}
// 编译Shader
auto shader = std::make_shared<GLShader>();
if (!shader->compileFromSource(vertSource.c_str(), fragSource.c_str())) {
E2D_ERROR("编译Shader '{}' 失败", name);
return nullptr;
}
// 存储Shader信息
ShaderInfo info;
info.shader = shader;
info.vertPath = vertPath;
info.fragPath = fragPath;
info.vertModifiedTime = getFileModifiedTime(vertPath);
info.fragModifiedTime = getFileModifiedTime(fragPath);
info.isBuiltin = false;
shaders_[name] = std::move(info);
E2D_INFO("加载Shader '{}' 成功", name);
return shader;
}
Ptr<GLShader> ShaderSystem::loadFromSource(const std::string& name,
const std::string& vertSource,
const std::string& fragSource) {
auto shader = std::make_shared<GLShader>();
if (!shader->compileFromSource(vertSource.c_str(), fragSource.c_str())) {
E2D_ERROR("编译Shader '{}' 失败", name);
return nullptr;
}
ShaderInfo info;
info.shader = shader;
info.vertModifiedTime = 0;
info.fragModifiedTime = 0;
info.isBuiltin = false;
shaders_[name] = std::move(info);
E2D_INFO("加载Shader '{}' 成功", name);
return shader;
}
Ptr<GLShader> ShaderSystem::get(const std::string& name) {
auto it = shaders_.find(name);
if (it != shaders_.end()) {
return it->second.shader;
}
return nullptr;
}
bool ShaderSystem::has(const std::string& name) const {
return shaders_.find(name) != shaders_.end();
}
void ShaderSystem::remove(const std::string& name) {
shaders_.erase(name);
}
void ShaderSystem::clear() {
shaders_.clear();
}
void ShaderSystem::setFileWatching(bool enable) {
fileWatching_ = enable;
if (enable) {
E2D_INFO("启用Shader文件监视");
} else {
E2D_INFO("禁用Shader文件监视");
}
}
void ShaderSystem::updateFileWatching() {
if (!fileWatching_) return;
watchTimer_ += 0.016f; // 假设60fps
if (watchTimer_ >= WATCH_INTERVAL) {
watchTimer_ = 0.0f;
checkAndReload();
}
}
void ShaderSystem::checkAndReload() {
for (auto& [name, info] : shaders_) {
if (info.isBuiltin) continue;
if (info.vertPath.empty() || info.fragPath.empty()) continue;
uint64_t vertTime = getFileModifiedTime(info.vertPath);
uint64_t fragTime = getFileModifiedTime(info.fragPath);
if (vertTime > info.vertModifiedTime || fragTime > info.fragModifiedTime) {
E2D_INFO("检测到Shader '{}' 文件变化,正在重载...", name);
reload(name);
}
}
}
bool ShaderSystem::reload(const std::string& name) {
auto it = shaders_.find(name);
if (it == shaders_.end()) {
E2D_ERROR("无法重载不存在的Shader '{}'", name);
return false;
}
auto& info = it->second;
if (info.isBuiltin) {
E2D_WARN("无法重载内置Shader '{}'", name);
return false;
}
if (info.vertPath.empty() || info.fragPath.empty()) {
E2D_ERROR("Shader '{}' 没有关联的文件路径", name);
return false;
}
// 重新加载
auto newShader = loadFromFile(name, info.vertPath, info.fragPath);
if (newShader) {
E2D_INFO("重载Shader '{}' 成功", name);
return true;
}
return false;
}
void ShaderSystem::reloadAll() {
for (const auto& [name, info] : shaders_) {
if (!info.isBuiltin) {
reload(name);
}
}
}
Ptr<GLShader> ShaderSystem::getBuiltinSpriteShader() {
return builtinSpriteShader_;
}
Ptr<GLShader> ShaderSystem::getBuiltinParticleShader() {
return builtinParticleShader_;
}
Ptr<GLShader> ShaderSystem::getBuiltinPostProcessShader() {
return builtinPostProcessShader_;
}
Ptr<GLShader> ShaderSystem::getBuiltinShapeShader() {
return builtinShapeShader_;
}
std::string ShaderSystem::readFile(const std::string& filepath) {
std::ifstream file(filepath, std::ios::in | std::ios::binary);
if (!file.is_open()) {
return "";
}
std::stringstream buffer;
buffer << file.rdbuf();
return buffer.str();
}
uint64_t ShaderSystem::getFileModifiedTime(const std::string& filepath) {
#ifdef _WIN32
struct _stat64 statBuf;
if (_stat64(filepath.c_str(), &statBuf) == 0) {
return static_cast<uint64_t>(statBuf.st_mtime);
}
#else
struct stat statBuf;
if (stat(filepath.c_str(), &statBuf) == 0) {
return static_cast<uint64_t>(statBuf.st_mtime);
}
#endif
return 0;
}
// ============================================================================
// ShaderParams实现
// ============================================================================
ShaderParams::ShaderParams(GLShader& shader) : shader_(shader) {}
ShaderParams& ShaderParams::setBool(const std::string& name, bool value) {
shader_.setBool(name, value);
return *this;
}
ShaderParams& ShaderParams::setInt(const std::string& name, int value) {
shader_.setInt(name, value);
return *this;
}
ShaderParams& ShaderParams::setFloat(const std::string& name, float value) {
shader_.setFloat(name, value);
return *this;
}
ShaderParams& ShaderParams::setVec2(const std::string& name, const glm::vec2& value) {
shader_.setVec2(name, value);
return *this;
}
ShaderParams& ShaderParams::setVec3(const std::string& name, const glm::vec3& value) {
shader_.setVec3(name, value);
return *this;
}
ShaderParams& ShaderParams::setVec4(const std::string& name, const glm::vec4& value) {
shader_.setVec4(name, value);
return *this;
}
ShaderParams& ShaderParams::setMat4(const std::string& name, const glm::mat4& value) {
shader_.setMat4(name, value);
return *this;
}
ShaderParams& ShaderParams::setColor(const std::string& name, const Color& color) {
shader_.setVec4(name, glm::vec4(color.r, color.g, color.b, color.a));
return *this;
}
} // namespace easy2d

View File

@ -1,134 +0,0 @@
#include <easy2d/graphics/vram_manager.h>
#include <easy2d/utils/logger.h>
#include <algorithm>
namespace easy2d {
// Switch 推荐 VRAM 预算 ~400MB
static constexpr size_t DEFAULT_VRAM_BUDGET = 400 * 1024 * 1024;
VRAMManager::VRAMManager()
: textureVRAM_(0)
, bufferVRAM_(0)
, vramBudget_(DEFAULT_VRAM_BUDGET)
, textureAllocCount_(0)
, textureFreeCount_(0)
, bufferAllocCount_(0)
, bufferFreeCount_(0)
, peakTextureVRAM_(0)
, peakBufferVRAM_(0) {
}
VRAMManager& VRAMManager::getInstance() {
static VRAMManager instance;
return instance;
}
void VRAMManager::allocTexture(size_t size) {
std::lock_guard<std::mutex> lock(mutex_);
textureVRAM_ += size;
textureAllocCount_++;
peakTextureVRAM_ = std::max(peakTextureVRAM_, textureVRAM_);
if (isOverBudget()) {
E2D_LOG_WARN("VRAM over budget! Used: {} MB / Budget: {} MB",
getUsedVRAM() / (1024 * 1024),
vramBudget_ / (1024 * 1024));
}
}
void VRAMManager::freeTexture(size_t size) {
std::lock_guard<std::mutex> lock(mutex_);
if (size <= textureVRAM_) {
textureVRAM_ -= size;
} else {
textureVRAM_ = 0;
}
textureFreeCount_++;
}
void VRAMManager::allocBuffer(size_t size) {
std::lock_guard<std::mutex> lock(mutex_);
bufferVRAM_ += size;
bufferAllocCount_++;
peakBufferVRAM_ = std::max(peakBufferVRAM_, bufferVRAM_);
if (isOverBudget()) {
E2D_LOG_WARN("VRAM over budget! Used: {} MB / Budget: {} MB",
getUsedVRAM() / (1024 * 1024),
vramBudget_ / (1024 * 1024));
}
}
void VRAMManager::freeBuffer(size_t size) {
std::lock_guard<std::mutex> lock(mutex_);
if (size <= bufferVRAM_) {
bufferVRAM_ -= size;
} else {
bufferVRAM_ = 0;
}
bufferFreeCount_++;
}
size_t VRAMManager::getUsedVRAM() const {
return textureVRAM_ + bufferVRAM_;
}
size_t VRAMManager::getTextureVRAM() const {
return textureVRAM_;
}
size_t VRAMManager::getBufferVRAM() const {
return bufferVRAM_;
}
size_t VRAMManager::getAvailableVRAM() const {
size_t used = getUsedVRAM();
return (used < vramBudget_) ? (vramBudget_ - used) : 0;
}
void VRAMManager::setVRAMBudget(size_t budget) {
std::lock_guard<std::mutex> lock(mutex_);
vramBudget_ = budget;
E2D_LOG_INFO("VRAM budget set to {} MB", budget / (1024 * 1024));
}
size_t VRAMManager::getVRAMBudget() const {
return vramBudget_;
}
bool VRAMManager::isOverBudget() const {
return getUsedVRAM() > vramBudget_;
}
void VRAMManager::printStats() const {
std::lock_guard<std::mutex> lock(mutex_);
E2D_LOG_INFO("=== VRAM Stats ===");
E2D_LOG_INFO(" Texture VRAM: {} MB (peak: {} MB)",
textureVRAM_ / (1024 * 1024),
peakTextureVRAM_ / (1024 * 1024));
E2D_LOG_INFO(" Buffer VRAM: {} MB (peak: {} MB)",
bufferVRAM_ / (1024 * 1024),
peakBufferVRAM_ / (1024 * 1024));
E2D_LOG_INFO(" Total Used: {} MB / {} MB budget",
(textureVRAM_ + bufferVRAM_) / (1024 * 1024),
vramBudget_ / (1024 * 1024));
E2D_LOG_INFO(" Texture allocs/frees: {} / {}",
textureAllocCount_, textureFreeCount_);
E2D_LOG_INFO(" Buffer allocs/frees: {} / {}",
bufferAllocCount_, bufferFreeCount_);
}
void VRAMManager::reset() {
std::lock_guard<std::mutex> lock(mutex_);
textureVRAM_ = 0;
bufferVRAM_ = 0;
textureAllocCount_ = 0;
textureFreeCount_ = 0;
bufferAllocCount_ = 0;
bufferFreeCount_ = 0;
peakTextureVRAM_ = 0;
peakBufferVRAM_ = 0;
}
} // namespace easy2d

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