feat: 添加推箱子游戏资源文件和基础功能
添加推箱子游戏所需的资源文件,包括字体、图片和音效资源。实现音频控制器、场景管理和UI组件等基础功能。重构部分代码以适配Extra2D框架,包括: - 音频控制逻辑 - 场景切换功能 - 游戏数据存储 - 动画系统基础 - 脚本绑定支持 - 事件系统 - 日志和资源管理 同时新增推箱子游戏的核心逻辑和场景: - 开始场景 - 游戏场景 - 成功场景 - 游戏数据管理 - 关卡逻辑
This commit is contained in:
parent
87cd46a403
commit
787a5688e2
|
|
@ -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
|
|
||||||
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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>;
|
|
||||||
}
|
|
||||||
|
|
@ -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)); }
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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 加载的Shader,失败返回nullptr
|
|
||||||
*/
|
|
||||||
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 加载的Shader,失败返回nullptr
|
|
||||||
*/
|
|
||||||
Ptr<GLShader> loadFromSource(const std::string& name,
|
|
||||||
const std::string& vertSource,
|
|
||||||
const std::string& fragSource);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从已编译的程序获取Shader
|
|
||||||
* @param name Shader名称
|
|
||||||
* @return 缓存的Shader,不存在返回nullptr
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -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>;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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
Loading…
Reference in New Issue