feat: 添加推箱子游戏示例及相关资源文件

添加推箱子游戏示例代码,包括游戏场景、音频控制器、UI按钮等核心组件。新增游戏资源文件如图片、音效、字体等。同时完善动画系统、脚本绑定、渲染后端等基础功能模块,并引入Squirrel脚本支持。

新增功能包括:
- 推箱子游戏核心逻辑与场景管理
- 音频播放与控制功能
- 游戏数据存储与读取
- 基础UI组件实现
- Squirrel脚本绑定系统
- OpenGL渲染后端支持
- 动画系统基础框架

资源文件包括:
- 游戏角色、箱子、墙壁等素材图片
- 背景音乐和音效文件
- 游戏字体文件
- 游戏配置文件
This commit is contained in:
ChestnutYueyue 2026-02-09 12:13:02 +08:00
commit 87cd46a403
256 changed files with 99307 additions and 0 deletions

View File

@ -0,0 +1,9 @@
{
"permissions": {
"allow": [
"Bash(xargs:*)",
"Bash(wc -l \"C:\\\\Users\\\\soulcoco\\\\Desktop\\\\Easy2D\\\\Easy2D-dev\\\\Easy2D\\\\src\"/**/*.cpp)",
"Bash(wc -l:*)"
]
}
}

141
.gitignore vendored Normal file
View File

@ -0,0 +1,141 @@
# ============================================
# Easy2D 项目 Git 忽略配置
# ============================================
# --------------------------------------------
# 构建输出目录
# --------------------------------------------
/build/
/bin/
/obj/
/out/
/Debug/
/Release/
/x64/
/x86/
/.build/
# --------------------------------------------
# xmake 构建系统
# --------------------------------------------
/.xmake/
/.xmake.conf
/xmake.config
# --------------------------------------------
# IDE 和编辑器配置
# --------------------------------------------
# Visual Studio
.vs/
*.sln
*.vcxproj
*.vcxproj.filters
*.vcxproj.user
*.suo
*.user
*.userosscache
*.sdf
*.opensdf
*.VC.db
*.VC.opendb
ipch/
# VS Code
.vscode/
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# CLion / IntelliJ
.idea/
*.iml
*.iws
*.ipr
cmake-build-*/
# --------------------------------------------
# 编译缓存和索引
# --------------------------------------------
/.cache/
/.clangd/
/compile_commands.json
.clang-format
.clang-tidy
# --------------------------------------------
# 依赖和包管理
# --------------------------------------------
/packages/
/deps/
/third_party/
/vendor/
# --------------------------------------------
# 生成的文件
# --------------------------------------------
*.exe
*.dll
*.lib
*.a
*.so
*.dylib
*.pdb
*.ilk
*.exp
*.manifest
*.res
*.o
*.obj
# --------------------------------------------
# 日志和临时文件
# --------------------------------------------
*.log
*.tmp
*.temp
*.swp
*.swo
*~
.DS_Store
Thumbs.db
# --------------------------------------------
# 测试和覆盖率
# --------------------------------------------
/test-results/
/coverage/
*.gcov
*.gcda
*.gcno
# --------------------------------------------
# 文档生成
# --------------------------------------------
/docs/html/
/docs/latex/
/doxygen/
# --------------------------------------------
# 平台特定
# --------------------------------------------
# Windows
*.bat
*.cmd
# macOS
.DS_Store
.AppleDouble
.LSOverride
# Linux
*.d
# --------------------------------------------
# 其他
# --------------------------------------------
*.zip
*.tar.gz
*.rar
*.7z

View File

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

View File

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

View File

@ -0,0 +1,118 @@
#include "data.h"
namespace pushbox {
int g_CurrentLevel = 1;
bool g_SoundOpen = true;
int g_Direct = 2;
bool g_Pushing = false;
Map g_Maps[MAX_LEVEL] = {
{
8, 8, 4, 4,
{
{{Empty}, {Empty}, {Wall}, {Wall}, {Wall}, {Empty}, {Empty}, {Empty}},
{{Empty}, {Empty}, {Wall}, {Ground, true}, {Wall}, {Empty}, {Empty}, {Empty}},
{{Empty}, {Empty}, {Wall}, {Ground}, {Wall}, {Wall}, {Wall}, {Wall}},
{{Wall}, {Wall}, {Wall}, {Box}, {Ground}, {Box}, {Ground, true}, {Wall}},
{{Wall}, {Ground, true}, {Ground}, {Box}, {Man}, {Wall}, {Wall}, {Wall}},
{{Wall}, {Wall}, {Wall}, {Wall}, {Box}, {Wall}, {Empty}, {Empty}},
{{Empty}, {Empty}, {Empty}, {Wall}, {Ground, true}, {Wall}, {Empty}, {Empty}},
{{Empty}, {Empty}, {Empty}, {Wall}, {Wall}, {Wall}, {Empty}, {Empty}},
},
},
{
9, 9, 1, 1,
{
{{Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Empty}, {Empty}, {Empty}, {Empty}},
{{Wall}, {Man}, {Ground}, {Ground}, {Wall}, {Empty}, {Empty}, {Empty}, {Empty}},
{{Wall}, {Ground}, {Box}, {Box}, {Wall}, {Empty}, {Wall}, {Wall}, {Wall}},
{{Wall}, {Ground}, {Box}, {Ground}, {Wall}, {Empty}, {Wall}, {Ground, true}, {Wall}},
{{Wall}, {Wall}, {Wall}, {Ground}, {Wall}, {Wall}, {Wall}, {Ground, true}, {Wall}},
{{Empty}, {Wall}, {Wall}, {Ground}, {Ground}, {Ground}, {Ground}, {Ground, true}, {Wall}},
{{Empty}, {Wall}, {Ground}, {Ground}, {Ground}, {Wall}, {Ground}, {Ground}, {Wall}},
{{Empty}, {Wall}, {Ground}, {Ground}, {Ground}, {Wall}, {Wall}, {Wall}, {Wall}},
{{Empty}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Empty}, {Empty}, {Empty}},
},
},
{
10, 7, 3, 3,
{
{{Empty}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Empty}, {Empty}},
{{Empty}, {Wall}, {Ground}, {Ground}, {Ground}, {Ground}, {Ground}, {Wall}, {Wall}, {Wall}},
{{Wall}, {Wall}, {Box}, {Wall}, {Wall}, {Wall}, {Ground}, {Ground}, {Ground}, {Wall}},
{{Wall}, {Ground}, {Ground}, {Man}, {Box}, {Ground}, {Ground}, {Box}, {Ground}, {Wall}},
{{Wall}, {Ground}, {Ground, true}, {Ground, true}, {Wall}, {Ground}, {Box}, {Ground}, {Wall}, {Wall}},
{{Wall}, {Wall}, {Ground, true}, {Ground, true}, {Wall}, {Ground}, {Ground}, {Ground}, {Wall}, {Empty}},
{{Empty}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Empty}},
},
},
{
6, 8, 1, 2,
{
{{Empty}, {Wall}, {Wall}, {Wall}, {Wall}, {Empty}},
{{Wall}, {Wall}, {Ground}, {Ground}, {Wall}, {Empty}},
{{Wall}, {Man}, {Box}, {Ground}, {Wall}, {Empty}},
{{Wall}, {Wall}, {Box}, {Ground}, {Wall}, {Wall}},
{{Wall}, {Wall}, {Ground}, {Box}, {Ground}, {Wall}},
{{Wall}, {Ground, true}, {Box}, {Ground}, {Ground}, {Wall}},
{{Wall}, {Ground, true}, {Ground, true}, {Box, true}, {Ground, true}, {Wall}},
{{Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}},
},
},
{
8, 8, 2, 2,
{
{{Empty}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Empty}, {Empty}},
{{Empty}, {Wall}, {Ground}, {Ground}, {Wall}, {Wall}, {Wall}, {Empty}},
{{Empty}, {Wall}, {Man}, {Box}, {Ground}, {Ground}, {Wall}, {Empty}},
{{Wall}, {Wall}, {Wall}, {Ground}, {Wall}, {Ground}, {Wall}, {Wall}},
{{Wall}, {Ground, true}, {Wall}, {Ground}, {Wall}, {Ground}, {Ground}, {Wall}},
{{Wall}, {Ground, true}, {Box}, {Ground}, {Ground}, {Wall}, {Ground}, {Wall}},
{{Wall}, {Ground, true}, {Ground}, {Ground}, {Ground}, {Box}, {Ground}, {Wall}},
{{Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}},
},
},
{
10, 8, 8, 1,
{
{{Empty}, {Empty}, {Empty}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}},
{{Empty}, {Empty}, {Wall}, {Wall}, {Ground}, {Ground}, {Wall}, {Ground}, {Man}, {Wall}},
{{Empty}, {Empty}, {Wall}, {Ground}, {Ground}, {Ground}, {Wall}, {Ground}, {Ground}, {Wall}},
{{Empty}, {Empty}, {Wall}, {Box}, {Ground}, {Box}, {Ground}, {Box}, {Ground}, {Wall}},
{{Empty}, {Empty}, {Wall}, {Ground}, {Box}, {Wall}, {Wall}, {Ground}, {Ground}, {Wall}},
{{Wall}, {Wall}, {Wall}, {Ground}, {Box}, {Ground}, {Wall}, {Ground}, {Wall}, {Wall}},
{{Wall}, {Ground, true}, {Ground, true}, {Ground, true}, {Ground, true}, {Ground, true}, {Ground}, {Ground}, {Wall}, {Empty}},
{{Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Empty}},
},
},
{
10, 7, 8, 3,
{
{{Empty}, {Empty}, {Empty}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Empty}},
{{Empty}, {Wall}, {Wall}, {Wall}, {Ground}, {Ground}, {Ground}, {Ground}, {Wall}, {Empty}},
{{Wall}, {Wall}, {Ground, true}, {Ground}, {Box}, {Wall}, {Wall}, {Ground}, {Wall}, {Wall}},
{{Wall}, {Ground, true}, {Ground, true}, {Box}, {Ground}, {Box}, {Ground}, {Ground}, {Man}, {Wall}},
{{Wall}, {Ground, true}, {Ground, true}, {Ground}, {Box}, {Ground}, {Box}, {Ground}, {Wall}, {Wall}},
{{Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Ground}, {Ground}, {Wall}, {Empty}},
{{Empty}, {Empty}, {Empty}, {Empty}, {Empty}, {Wall}, {Wall}, {Wall}, {Wall}, {Empty}},
},
},
{
11, 9, 8, 7,
{
{{Empty}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Empty}},
{{Empty}, {Wall}, {Ground}, {Ground}, {Wall}, {Wall}, {Ground}, {Ground}, {Ground}, {Wall}, {Empty}},
{{Empty}, {Wall}, {Ground}, {Ground}, {Ground}, {Box}, {Ground}, {Ground}, {Ground}, {Wall}, {Empty}},
{{Empty}, {Wall}, {Box}, {Ground}, {Wall}, {Wall}, {Wall}, {Ground}, {Box}, {Wall}, {Empty}},
{{Empty}, {Wall}, {Ground}, {Wall}, {Ground, true}, {Ground, true}, {Ground, true}, {Wall}, {Ground}, {Wall}, {Empty}},
{{Wall}, {Wall}, {Ground}, {Wall}, {Ground, true}, {Ground, true}, {Ground, true}, {Wall}, {Ground}, {Wall}, {Wall}},
{{Wall}, {Ground}, {Box}, {Ground}, {Ground}, {Box}, {Ground}, {Ground}, {Box}, {Ground}, {Wall}},
{{Wall}, {Ground}, {Ground}, {Ground}, {Ground}, {Ground}, {Wall}, {Ground}, {Man}, {Ground}, {Wall}},
{{Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}},
},
},
};
} // namespace pushbox

View File

@ -0,0 +1,29 @@
#pragma once
#define MAX_LEVEL 8
namespace pushbox {
enum TYPE { Empty, Wall, Ground, Box, Man };
struct Piece {
TYPE type;
bool isPoint;
};
struct Map {
int width;
int height;
int roleX;
int roleY;
Piece value[12][12];
};
extern Map g_Maps[MAX_LEVEL];
extern int g_CurrentLevel;
extern bool g_SoundOpen;
extern int g_Direct;
extern bool g_Pushing;
} // namespace pushbox

View File

@ -0,0 +1,148 @@
#include "storage.h"
#include <easy2d/utils/data.h>
#include <fstream>
#include <sstream>
#include <string>
namespace pushbox {
static easy2d::DataStore g_store;
static std::filesystem::path g_filePath;
static bool g_loaded = false;
// 默认配置内容
static const char* DEFAULT_CONFIG = R"([game]
level = 1
sound = true
[best]
)";
/**
* @brief romfs
* @return
* @note Switch romfs romfs:/pushbox.ini
*/
static std::string loadDefaultConfigFromRomfs() {
// 尝试多个可能的路径(按优先级排序)
const char* paths[] = {
"romfs:/pushbox.ini", // Switch romfs 正确路径格式
"romfs/pushbox.ini", // 开发环境相对路径
"pushbox.ini", // 当前目录
};
for (const char* path : paths) {
if (std::filesystem::exists(path)) {
std::ifstream file(path, std::ios::binary);
if (file) {
std::ostringstream buffer;
buffer << file.rdbuf();
return buffer.str();
}
}
}
// 如果找不到文件,返回内置的默认配置
return DEFAULT_CONFIG;
}
/**
* @brief
* @param content
* @return
*/
static std::filesystem::path writeConfigToTempFile(const std::string& content) {
auto tempPath = std::filesystem::temp_directory_path() / "pushbox_default.ini";
std::ofstream file(tempPath, std::ios::binary);
if (file) {
file << content;
file.close();
}
return tempPath;
}
static void ensureLoaded() {
if (g_loaded) {
return;
}
// 首先尝试从可执行目录加载用户配置
if (!g_filePath.empty() && std::filesystem::exists(g_filePath)) {
g_store.load(g_filePath.string());
} else {
// 从 romfs 加载默认配置
std::string defaultConfig = loadDefaultConfigFromRomfs();
if (!defaultConfig.empty()) {
auto tempPath = writeConfigToTempFile(defaultConfig);
g_store.load(tempPath.string());
}
}
g_loaded = true;
}
void initStorage(const std::filesystem::path& baseDir) {
g_filePath = baseDir / "pushbox.ini";
// 首先尝试从可执行目录加载用户配置
if (std::filesystem::exists(g_filePath)) {
g_store.load(g_filePath.string());
} else {
// 从 romfs 加载默认配置
std::string defaultConfig = loadDefaultConfigFromRomfs();
if (!defaultConfig.empty()) {
auto tempPath = writeConfigToTempFile(defaultConfig);
g_store.load(tempPath.string());
}
}
g_loaded = true;
}
int loadCurrentLevel(int defaultValue) {
ensureLoaded();
int level = g_store.getInt("game", "level", defaultValue);
if (level < 1) {
level = 1;
}
return level;
}
void saveCurrentLevel(int level) {
ensureLoaded();
g_store.setInt("game", "level", level);
if (!g_filePath.empty()) {
g_store.save(g_filePath.string());
}
}
bool loadSoundOpen(bool defaultValue) {
ensureLoaded();
return g_store.getBool("game", "sound", defaultValue);
}
void saveSoundOpen(bool open) {
ensureLoaded();
g_store.setBool("game", "sound", open);
if (!g_filePath.empty()) {
g_store.save(g_filePath.string());
}
}
int loadBestStep(int level, int defaultValue) {
ensureLoaded();
std::string key = "level" + std::to_string(level);
return g_store.getInt("best", key, defaultValue);
}
void saveBestStep(int level, int step) {
ensureLoaded();
std::string key = "level" + std::to_string(level);
g_store.setInt("best", key, step);
if (!g_filePath.empty()) {
g_store.save(g_filePath.string());
}
}
std::filesystem::path storageFilePath() { return g_filePath; }
} // namespace pushbox

View File

@ -0,0 +1,21 @@
#pragma once
#include <filesystem>
namespace pushbox {
void initStorage(const std::filesystem::path& baseDir);
int loadCurrentLevel(int defaultValue = 1);
void saveCurrentLevel(int level);
bool loadSoundOpen(bool defaultValue = true);
void saveSoundOpen(bool open);
int loadBestStep(int level, int defaultValue = 0);
void saveBestStep(int level, int step);
std::filesystem::path storageFilePath();
} // namespace pushbox

View File

@ -0,0 +1,103 @@
#include <easy2d/easy2d.h>
#include <filesystem>
// Pushbox 游戏核心头文件
#include "core/data.h"
#include "core/storage.h"
#include "scenes/start_scene.h"
// Nintendo Switch 平台支持
#ifdef __SWITCH__
#include <switch.h>
#endif
// Switch 上的存储路径
#ifdef __SWITCH__
static const char *SWITCH_STORAGE_PATH = "sdmc:/switch/pushbox";
#endif
static std::filesystem::path getExecutableDir(int argc, char **argv) {
// Nintendo Switch 上使用 SD 卡路径
#ifdef __SWITCH__
// 创建目录(如果不存在)
std::filesystem::create_directories(SWITCH_STORAGE_PATH);
return SWITCH_STORAGE_PATH;
#else
if (argc <= 0 || argv == nullptr || argv[0] == nullptr) {
return std::filesystem::current_path();
}
std::error_code ec;
auto exePath = std::filesystem::absolute(argv[0], ec);
if (ec) {
return std::filesystem::current_path();
}
return exePath.parent_path();
#endif
}
static float parseAutoQuitSeconds(int argc, char **argv) {
for (int i = 1; i < argc; i++) {
if (!argv[i]) {
continue;
}
const std::string arg = argv[i];
const std::string prefix = "--autoquit=";
if (arg.rfind(prefix, 0) == 0) {
try {
return std::stof(arg.substr(prefix.size()));
} catch (...) {
return 0.0f;
}
}
}
return 0.0f;
}
int main(int argc, char **argv) {
easy2d::Logger::init();
easy2d::Logger::setLevel(easy2d::LogLevel::Info);
auto &app = easy2d::Application::instance();
easy2d::AppConfig config;
config.title = "推箱子";
config.width = 640;
config.height = 480;
config.vsync = true;
config.fpsLimit = 0;
if (!app.init(config)) {
easy2d::Logger::shutdown();
return -1;
}
const auto exeDir = getExecutableDir(argc, argv);
auto &resources = app.resources();
resources.addSearchPath(exeDir.string());
resources.addSearchPath((exeDir / "assets").string());
resources.addSearchPath((exeDir.parent_path() / "assets").string());
resources.addSearchPath((exeDir.parent_path() / "src").string());
resources.addSearchPath("assets");
resources.addSearchPath("src");
pushbox::initStorage(exeDir);
pushbox::g_CurrentLevel = pushbox::loadCurrentLevel(1);
if (pushbox::g_CurrentLevel > MAX_LEVEL) {
pushbox::g_CurrentLevel = 1;
}
pushbox::g_SoundOpen = pushbox::loadSoundOpen(true);
// 进入开始场景(主界面)
app.enterScene(easy2d::makePtr<pushbox::StartScene>());
const float autoQuitSeconds = parseAutoQuitSeconds(argc, argv);
if (autoQuitSeconds > 0.0f) {
app.timers().addTimer(autoQuitSeconds, [&app]() { app.quit(); });
}
app.run();
app.shutdown();
easy2d::Logger::shutdown();
return 0;
}

View File

@ -0,0 +1,61 @@
#include "audio_controller.h"
#include "../core/storage.h"
namespace pushbox {
easy2d::Ptr<AudioController> AudioController::create() { return easy2d::makePtr<AudioController>(); }
void AudioController::onEnter() {
Node::onEnter();
if (!loaded_) {
auto& resources = easy2d::Application::instance().resources();
background_ = resources.loadSound("pushbox_bg", "assets/audio/background.wav");
manMove_ = resources.loadSound("pushbox_manmove", "assets/audio/manmove.wav");
boxMove_ = resources.loadSound("pushbox_boxmove", "assets/audio/boxmove.wav");
if (background_) {
background_->setLooping(true);
background_->play();
}
loaded_ = true;
}
setEnabled(g_SoundOpen);
}
void AudioController::setEnabled(bool enabled) {
enabled_ = enabled;
g_SoundOpen = enabled;
saveSoundOpen(enabled);
if (!background_) {
return;
}
if (enabled_) {
background_->resume();
} else {
background_->pause();
}
}
void AudioController::playManMove() {
if (!enabled_ || !manMove_) {
return;
}
manMove_->play();
}
void AudioController::playBoxMove() {
if (!enabled_ || !boxMove_) {
return;
}
boxMove_->play();
}
} // namespace pushbox

View File

@ -0,0 +1,30 @@
#pragma once
#include "../core/data.h"
#include <easy2d/easy2d.h>
namespace pushbox {
class AudioController : public easy2d::Node {
public:
static easy2d::Ptr<AudioController> create();
void onEnter() override;
void setEnabled(bool enabled);
bool isEnabled() const { return enabled_; }
void playManMove();
void playBoxMove();
private:
bool loaded_ = false;
bool enabled_ = true;
easy2d::Ptr<easy2d::Sound> background_;
easy2d::Ptr<easy2d::Sound> manMove_;
easy2d::Ptr<easy2d::Sound> boxMove_;
};
} // namespace pushbox

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 477 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 682 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 B

View File

@ -0,0 +1,27 @@
; Pushbox Game Configuration File
; 推箱子游戏配置文件
[game]
; 当前关卡 (1-15)
level = 1
; 声音开关 (true/false)
sound = true
[best]
; 各关卡最佳步数记录
; level1 = 0
; level2 = 0
; level3 = 0
; level4 = 0
; level5 = 0
; level6 = 0
; level7 = 0
; level8 = 0
; level9 = 0
; level10 = 0
; level11 = 0
; level12 = 0
; level13 = 0
; level14 = 0
; level15 = 0

View File

@ -0,0 +1,333 @@
#include "play_scene.h"
#include "../core/audio_context.h"
#include "../core/storage.h"
#include "../nodes/audio_controller.h"
#include "start_scene.h"
#include "success_scene.h"
#include <easy2d/easy2d.h>
namespace pushbox {
static easy2d::Ptr<easy2d::FontAtlas> loadFont(int size) {
auto& resources = easy2d::Application::instance().resources();
return resources.loadFont("assets/font.ttf", size);
}
PlayScene::PlayScene(int level) {
setBackgroundColor(easy2d::Colors::Black);
// 设置视口大小为窗口尺寸
auto& app = easy2d::Application::instance();
auto& config = app.getConfig();
setViewportSize(static_cast<float>(config.width), static_cast<float>(config.height));
auto& resources = app.resources();
E2D_LOG_INFO("PlayScene: Loading textures...");
texWall_ = resources.loadTexture("assets/images/wall.gif");
E2D_LOG_INFO("wall texture: {}", texWall_ ? "OK" : "FAILED");
texPoint_ = resources.loadTexture("assets/images/point.gif");
texFloor_ = resources.loadTexture("assets/images/floor.gif");
texBox_ = resources.loadTexture("assets/images/box.gif");
texBoxInPoint_ = resources.loadTexture("assets/images/boxinpoint.gif");
if (!texWall_ || !texFloor_ || !texBox_ || !texBoxInPoint_) {
E2D_LOG_ERROR("PlayScene: Failed to load basic textures!");
}
texMan_[1] = resources.loadTexture("assets/images/player/manup.gif");
texMan_[2] = resources.loadTexture("assets/images/player/mandown.gif");
texMan_[3] = resources.loadTexture("assets/images/player/manleft.gif");
texMan_[4] = resources.loadTexture("assets/images/player/manright.gif");
texManPush_[1] = resources.loadTexture("assets/images/player/manhandup.gif");
texManPush_[2] = resources.loadTexture("assets/images/player/manhanddown.gif");
texManPush_[3] = resources.loadTexture("assets/images/player/manhandleft.gif");
texManPush_[4] = resources.loadTexture("assets/images/player/manhandright.gif");
font28_ = loadFont(28);
font20_ = loadFont(20);
if (!font28_ || !font20_) {
E2D_LOG_ERROR("PlayScene: Failed to load fonts!");
}
if (font28_) {
levelText_ = easy2d::Text::create("", font28_);
levelText_->setPosition(520.0f, 30.0f);
levelText_->setTextColor(easy2d::Colors::White);
addChild(levelText_);
}
if (font20_) {
stepText_ = easy2d::Text::create("", font20_);
stepText_->setPosition(520.0f, 100.0f);
stepText_->setTextColor(easy2d::Colors::White);
addChild(stepText_);
bestText_ = easy2d::Text::create("", font20_);
bestText_->setPosition(520.0f, 140.0f);
bestText_->setTextColor(easy2d::Colors::White);
addChild(bestText_);
auto exitText = easy2d::Text::create("按ESC返回", font20_);
exitText->setPosition(520.0f, 250.0f);
exitText->setTextColor(easy2d::Colors::White);
addChild(exitText);
auto restartText = easy2d::Text::create("按回车重开", font20_);
restartText->setPosition(520.0f, 290.0f);
restartText->setTextColor(easy2d::Colors::White);
addChild(restartText);
}
auto soundOn = resources.loadTexture("assets/images/soundon.png");
auto soundOff = resources.loadTexture("assets/images/soundoff.png");
if (soundOn && soundOff) {
soundBtn_ = easy2d::ToggleImageButton::create();
soundBtn_->setStateImages(soundOff, soundOn);
soundBtn_->setCustomSize(static_cast<float>(soundOn->getWidth()),
static_cast<float>(soundOn->getHeight()));
soundBtn_->setBorder(easy2d::Colors::Transparent, 0.0f);
soundBtn_->setPosition(560.0f, 360.0f);
soundBtn_->setOnStateChange([](bool on) {
if (auto audio = getAudioController()) {
audio->setEnabled(on);
}
});
addChild(soundBtn_);
}
mapLayer_ = easy2d::makePtr<easy2d::Node>();
mapLayer_->setAnchor(0.0f, 0.0f);
mapLayer_->setPosition(0.0f, 0.0f);
addChild(mapLayer_);
// 创建音频控制器
auto audioNode = AudioController::create();
audioNode->setName("AudioController");
addChild(audioNode);
setAudioController(audioNode);
setLevel(level);
}
void PlayScene::onEnter() {
Scene::onEnter();
if (soundBtn_) {
soundBtn_->setOn(g_SoundOpen);
}
}
void PlayScene::onUpdate(float dt) {
Scene::onUpdate(dt);
auto& app = easy2d::Application::instance();
auto& input = app.input();
if (input.isKeyPressed(easy2d::Key::Escape)) {
app.scenes().replaceScene(
easy2d::makePtr<StartScene>(), easy2d::TransitionType::Fade, 0.2f);
return;
}
if (input.isKeyPressed(easy2d::Key::Enter)) {
setLevel(g_CurrentLevel);
return;
}
if (input.isKeyPressed(easy2d::Key::Up)) {
move(0, -1, 1);
flush();
} else if (input.isKeyPressed(easy2d::Key::Down)) {
move(0, 1, 2);
flush();
} else if (input.isKeyPressed(easy2d::Key::Left)) {
move(-1, 0, 3);
flush();
} else if (input.isKeyPressed(easy2d::Key::Right)) {
move(1, 0, 4);
flush();
} else {
return;
}
for (int i = 0; i < map_.width; i++) {
for (int j = 0; j < map_.height; j++) {
Piece p = map_.value[j][i];
if (p.type == TYPE::Box && p.isPoint == false) {
return;
}
}
}
gameOver();
}
void PlayScene::flush() {
mapLayer_->removeAllChildren();
int tileW = texFloor_ ? texFloor_->getWidth() : 32;
int tileH = texFloor_ ? texFloor_->getHeight() : 32;
float offsetX = static_cast<float>((12 - map_.width) / 2) * tileW;
float offsetY = static_cast<float>((12 - map_.height) / 2) * tileH;
for (int i = 0; i < map_.width; i++) {
for (int j = 0; j < map_.height; j++) {
Piece piece = map_.value[j][i];
easy2d::Ptr<easy2d::Texture> tex;
if (piece.type == TYPE::Wall) {
tex = texWall_;
} else if (piece.type == TYPE::Ground && piece.isPoint) {
tex = texPoint_;
} else if (piece.type == TYPE::Ground) {
tex = texFloor_;
} else if (piece.type == TYPE::Box && piece.isPoint) {
tex = texBoxInPoint_;
} else if (piece.type == TYPE::Box) {
tex = texBox_;
} else if (piece.type == TYPE::Man && g_Pushing) {
tex = texManPush_[g_Direct];
} else if (piece.type == TYPE::Man) {
tex = texMan_[g_Direct];
} else {
continue;
}
if (!tex) {
continue;
}
auto sprite = easy2d::Sprite::create(tex);
sprite->setAnchor(0.0f, 0.0f);
sprite->setPosition(offsetX + static_cast<float>(i * tileW),
offsetY + static_cast<float>(j * tileH));
mapLayer_->addChild(sprite);
}
}
}
void PlayScene::setLevel(int level) {
g_CurrentLevel = level;
saveCurrentLevel(g_CurrentLevel);
if (levelText_) {
levelText_->setText("" + std::to_string(level) + "");
}
setStep(0);
int bestStep = loadBestStep(level, 0);
if (bestText_) {
if (bestStep != 0) {
bestText_->setText("最佳" + std::to_string(bestStep) + "");
} else {
bestText_->setText("");
}
}
map_ = g_Maps[level - 1];
g_Direct = 2;
g_Pushing = false;
flush();
}
void PlayScene::setStep(int step) {
step_ = step;
if (stepText_) {
stepText_->setText("当前" + std::to_string(step) + "");
}
}
void PlayScene::move(int dx, int dy, int direct) {
int targetX = dx + map_.roleX;
int targetY = dy + map_.roleY;
g_Direct = direct;
if (targetX < 0 || targetX >= map_.width || targetY < 0 || targetY >= map_.height) {
return;
}
if (map_.value[targetY][targetX].type == TYPE::Wall) {
return;
}
if (map_.value[targetY][targetX].type == TYPE::Ground) {
g_Pushing = false;
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
map_.value[targetY][targetX].type = TYPE::Man;
if (auto audio = getAudioController()) {
audio->playManMove();
}
} else if (map_.value[targetY][targetX].type == TYPE::Box) {
g_Pushing = true;
int boxX = 0;
int boxY = 0;
switch (g_Direct) {
case 1:
boxX = targetX;
boxY = targetY - 1;
break;
case 2:
boxX = targetX;
boxY = targetY + 1;
break;
case 3:
boxX = targetX - 1;
boxY = targetY;
break;
case 4:
boxX = targetX + 1;
boxY = targetY;
break;
default:
return;
}
if (boxX < 0 || boxX >= map_.width || boxY < 0 || boxY >= map_.height) {
return;
}
if (map_.value[boxY][boxX].type == TYPE::Wall || map_.value[boxY][boxX].type == TYPE::Box) {
return;
}
map_.value[boxY][boxX].type = TYPE::Box;
map_.value[targetY][targetX].type = TYPE::Man;
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
if (auto audio = getAudioController()) {
audio->playBoxMove();
}
} else {
return;
}
map_.roleX = targetX;
map_.roleY = targetY;
setStep(step_ + 1);
}
void PlayScene::gameOver() {
int bestStep = loadBestStep(g_CurrentLevel, 0);
if (bestStep == 0 || step_ < bestStep) {
saveBestStep(g_CurrentLevel, step_);
}
if (g_CurrentLevel == MAX_LEVEL) {
easy2d::Application::instance().scenes().pushScene(easy2d::makePtr<SuccessScene>(),
easy2d::TransitionType::Fade, 0.25f);
return;
}
setLevel(g_CurrentLevel + 1);
}
} // namespace pushbox

View File

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

View File

@ -0,0 +1,126 @@
#include "start_scene.h"
#include "../core/audio_context.h"
#include "../core/data.h"
#include "../nodes/audio_controller.h"
#include "../scenes/play_scene.h"
#include "../ui/menu_button.h"
#include <easy2d/easy2d.h>
namespace pushbox {
StartScene::StartScene() {
// 设置视口大小为窗口尺寸
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 StartScene::onEnter() {
Scene::onEnter();
E2D_LOG_INFO("StartScene::onEnter() - BEGIN");
auto& app = easy2d::Application::instance();
auto& resources = app.resources();
// 设置红色背景用于测试渲染
setBackgroundColor(easy2d::Color(1.0f, 0.0f, 0.0f, 1.0f));
E2D_LOG_INFO("StartScene: Background color set to RED for testing");
if (getChildren().empty()) {
E2D_LOG_INFO("StartScene: Creating audio controller...");
auto audioNode = AudioController::create();
audioNode->setName("audio_controller");
addChild(audioNode);
setAudioController(audioNode);
E2D_LOG_INFO("StartScene: Audio controller created");
E2D_LOG_INFO("StartScene: Loading background texture...");
auto bgTex = resources.loadTexture("assets/images/start.jpg");
if (bgTex) {
E2D_LOG_INFO("StartScene: Background texture loaded successfully");
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);
E2D_LOG_INFO("StartScene: Background sprite added");
} else {
E2D_LOG_ERROR("StartScene: Failed to load background texture");
}
E2D_LOG_INFO("StartScene: Loading font...");
font_ = loadMenuFont();
if (font_) {
E2D_LOG_INFO("StartScene: Font loaded successfully, creating menu buttons");
// 字体加载成功,创建菜单按钮
auto startBtn = MenuButton::create(font_, "新游戏", [this]() { startNewGame(); });
startBtn->setPosition(app.getConfig().width / 2.0f, 260.0f);
addChild(startBtn);
resumeBtn_ = MenuButton::create(font_, "继续关卡", [this]() { continueGame(); });
resumeBtn_->setPosition(app.getConfig().width / 2.0f, 300.0f);
addChild(resumeBtn_);
auto exitBtn = MenuButton::create(font_, "退出", [this]() { exitGame(); });
exitBtn->setPosition(app.getConfig().width / 2.0f, 340.0f);
addChild(exitBtn);
E2D_LOG_INFO("StartScene: Menu buttons created");
} else {
E2D_LOG_ERROR("StartScene: Failed to load font, menu buttons will not be displayed");
}
E2D_LOG_INFO("StartScene: Loading sound icons...");
auto soundOn = resources.loadTexture("assets/images/soundon.png");
auto soundOff = resources.loadTexture("assets/images/soundoff.png");
if (soundOn && soundOff) {
E2D_LOG_INFO("StartScene: Sound icons loaded successfully");
soundBtn_ = easy2d::ToggleImageButton::create();
soundBtn_->setStateImages(soundOff, soundOn);
soundBtn_->setCustomSize(static_cast<float>(soundOn->getWidth()),
static_cast<float>(soundOn->getHeight()));
soundBtn_->setBorder(easy2d::Colors::Transparent, 0.0f);
soundBtn_->setPosition(50.0f, 50.0f);
soundBtn_->setOnStateChange([](bool on) {
if (auto audio = getAudioController()) {
audio->setEnabled(on);
}
});
addChild(soundBtn_);
} else {
E2D_LOG_WARN("StartScene: Failed to load sound icons");
}
}
if (resumeBtn_) {
resumeBtn_->setEnabled(g_CurrentLevel != 1);
}
if (soundBtn_) {
soundBtn_->setOn(g_SoundOpen);
}
E2D_LOG_INFO("StartScene::onEnter() - END");
}
void StartScene::startNewGame() {
easy2d::Application::instance().scenes().replaceScene(
easy2d::makePtr<PlayScene>(1), easy2d::TransitionType::Fade, 0.25f);
}
void StartScene::continueGame() {
easy2d::Application::instance().scenes().replaceScene(
easy2d::makePtr<PlayScene>(g_CurrentLevel), easy2d::TransitionType::Fade, 0.25f);
}
void StartScene::exitGame() { easy2d::Application::instance().quit(); }
} // namespace pushbox

View File

@ -0,0 +1,24 @@
#pragma once
#include <easy2d/easy2d.h>
namespace pushbox {
class MenuButton;
class StartScene : public easy2d::Scene {
public:
StartScene();
void onEnter() override;
private:
void startNewGame();
void continueGame();
void exitGame();
easy2d::Ptr<MenuButton> resumeBtn_;
easy2d::Ptr<easy2d::ToggleImageButton> soundBtn_;
easy2d::Ptr<easy2d::FontAtlas> font_;
};
} // namespace pushbox

View File

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

View File

@ -0,0 +1,13 @@
#pragma once
#include <easy2d/easy2d.h>
namespace pushbox {
class SuccessScene : public easy2d::Scene {
public:
SuccessScene();
void onEnter() override;
};
} // namespace pushbox

View File

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

View File

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

View File

@ -0,0 +1,151 @@
#include <easy2d/easy2d.h>
#include <iostream>
// Nintendo Switch 平台支持
#ifdef __SWITCH__
#include <switch.h>
#endif
using namespace easy2d;
// 加载系统字体的辅助函数
easy2d::Ptr<easy2d::FontAtlas> loadSystemFont(int size) {
auto &resources = Application::instance().resources();
easy2d::Ptr<easy2d::FontAtlas> font = nullptr;
#ifdef __SWITCH__
// Nintendo Switch 系统字体路径
const char *switchFontPaths[] = {
"romfs:/font.TTF", // RomFS 中的字体(注意大小写)
"romfs:/font.ttf", // 小写备选
"sdmc:/switch/pushbox/font.ttf", // SD 卡字体
"/switch/pushbox/font.ttf", // 绝对路径
};
for (auto *path : switchFontPaths) {
font = resources.loadFont(path, size);
if (font) {
E2D_LOG_INFO("Loaded Switch font: %s", path);
return font;
}
}
#else
// Windows 系统字体
const char *winFontPaths[] = {
"C:/Windows/Fonts/arial.ttf",
"C:/Windows/Fonts/segoeui.ttf",
"C:/Windows/Fonts/simsun.ttc",
"C:/Windows/Fonts/simhei.ttf",
};
for (auto *path : winFontPaths) {
font = resources.loadFont(path, size);
if (font) {
E2D_LOG_INFO("Loaded Windows font: %s", path);
return font;
}
}
#endif
E2D_LOG_WARN("Failed to load any system font!");
return nullptr;
}
class SimpleScene : public Scene {
public:
SimpleScene() {
// 设置背景颜色为深蓝色 (使用 RGB 值)
setBackgroundColor(Color(0.0f, 0.0f, 0.5f, 1.0f));
// 创建一个红色填充矩形(用于测试渲染)
// 矩形在屏幕左上角,大小 200x200
Rect rectBounds(50, 50, 200, 200); // x, y, width, height
auto rect = ShapeNode::createFilledRect(rectBounds, Colors::Red);
addChild(rect);
// 创建一个黄色圆形
auto circle = ShapeNode::createFilledCircle(Vec2(400, 300), 100, Colors::Yellow);
addChild(circle);
// 创建一个绿色三角形
auto triangle = ShapeNode::createFilledTriangle(
Vec2(700, 200), Vec2(600, 400), Vec2(800, 400), Colors::Green);
addChild(triangle);
// 创建一个简单的标签
auto label = Text::create("Hello Switch!");
// 加载系统字体
auto font = loadSystemFont(48);
if (font) {
label->setFont(font);
E2D_LOG_INFO("Font loaded successfully!");
} else {
E2D_LOG_WARN("Font loading failed!");
}
label->setTextColor(Colors::White);
label->setPosition(640, 100); // 屏幕上方居中
label->setAnchor(0.5f, 0.5f);
addChild(label);
E2D_LOG_INFO("SimpleScene created successfully!");
}
};
int main(int argc, char **argv) {
// Nintendo Switch 初始化
#ifdef __SWITCH__
Result rc;
// 初始化 nxlink 调试输出(可选)
rc = socketInitializeDefault();
if (R_FAILED(rc)) {
std::cout << "socketInitializeDefault failed" << std::endl;
} else {
nxlinkStdio();
std::cout << "nxlink initialized!" << std::endl;
}
// 初始化 RomFS可选
rc = romfsInit();
if (R_FAILED(rc)) {
std::cout << "romfsInit failed" << std::endl;
}
#endif
std::cout << "Starting Easy2D Simple Test..." << std::endl;
// 配置应用
AppConfig config;
config.title = "Switch Simple Test";
config.width = 1280;
config.height = 720;
// 初始化 Easy2D
if (!Application::instance().init(config)) {
std::cerr << "Failed to initialize Easy2D!" << std::endl;
return -1;
}
std::cout << "Easy2D initialized successfully!" << std::endl;
// 创建场景并设置到场景管理器
auto scene = std::make_shared<SimpleScene>();
Application::instance().scenes().pushScene(scene);
std::cout << "Scene started!" << std::endl;
// 运行主循环
Application::instance().run();
// 清理
Application::instance().shutdown();
#ifdef __SWITCH__
romfsExit();
socketExit();
#endif
return 0;
}

Binary file not shown.

View File

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

View File

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

View File

@ -0,0 +1,68 @@
#pragma once
namespace easy2d {
// Easing function type
using EaseFunction = float (*)(float);
// Linear (no easing)
float easeLinear(float t);
// Quadratic
float easeInQuad(float t);
float easeOutQuad(float t);
float easeInOutQuad(float t);
// Cubic
float easeInCubic(float t);
float easeOutCubic(float t);
float easeInOutCubic(float t);
// Quartic
float easeInQuart(float t);
float easeOutQuart(float t);
float easeInOutQuart(float t);
// Quintic
float easeInQuint(float t);
float easeOutQuint(float t);
float easeInOutQuint(float t);
// Sine
float easeInSine(float t);
float easeOutSine(float t);
float easeInOutSine(float t);
// Exponential
float easeInExpo(float t);
float easeOutExpo(float t);
float easeInOutExpo(float t);
// Circular
float easeInCirc(float t);
float easeOutCirc(float t);
float easeInOutCirc(float t);
// Back
float easeInBack(float t);
float easeOutBack(float t);
float easeInOutBack(float t);
// Elastic
float easeInElastic(float t);
float easeOutElastic(float t);
float easeInOutElastic(float t);
// Bounce
float easeInBounce(float t);
float easeOutBounce(float t);
float easeInOutBounce(float t);
// Ease Action wrapper
class Action;
class EaseAction
{
public:
static Action* create(Action* action, EaseFunction easeFunc);
};
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,194 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/core/math_types.h>
#include <easy2d/core/color.h>
#include <variant>
#include <unordered_map>
#include <optional>
#include <any>
#include <string>
#include <vector>
namespace easy2d {
// ============================================================================
// 帧属性键 - 强类型枚举替代原始 ANI 的字符串键
// ============================================================================
enum class FramePropertyKey : uint32 {
// 事件触发
SetFlag = 0x0001, // int: 关键帧回调索引
PlaySound = 0x0002, // string: 音效路径
// 变换属性
ImageRate = 0x0010, // Vec2: 缩放比例
ImageRotate = 0x0011, // float: 旋转角度(度)
ImageOffset = 0x0012, // Vec2: 额外位置偏移
// 视觉效果
BlendLinearDodge = 0x0020, // bool: 线性减淡
BlendAdditive = 0x0021, // bool: 加法混合
ColorTint = 0x0022, // Color: RGBA 颜色
// 控制标记
Interpolation = 0x0030, // bool: 启用到下一帧的插值
Loop = 0x0031, // bool: 全局循环标记
// DNF ANI 扩展属性
DamageType = 0x0040, // int: 伤害类型 (0=Normal, 1=SuperArmor, 2=Unbreakable)
Shadow = 0x0041, // bool: 阴影
FlipType = 0x0042, // int: 翻转类型 (1=Horizon, 2=Vertical, 3=All)
Coord = 0x0043, // int: 坐标系
LoopStart = 0x0044, // bool: 循环起始标记
LoopEnd = 0x0045, // int: 循环结束帧数
GraphicEffect = 0x0046, // int: 图形特效类型
ClipRegion = 0x0047, // vector<int>: 裁剪区域 [4个int16]
// 用户自定义扩展区间 (0x1000+)
UserDefined = 0x1000,
};
// ============================================================================
// 帧属性值 - variant 多态值
// ============================================================================
using FramePropertyValue = std::variant<
bool,
int,
float,
std::string,
Vec2,
Color,
std::vector<int>
>;
// ============================================================================
// FramePropertyKey 的 hash 支持
// ============================================================================
struct FramePropertyKeyHash {
size_t operator()(FramePropertyKey key) const noexcept {
return std::hash<uint32>{}(static_cast<uint32>(key));
}
};
// ============================================================================
// FramePropertySet - 单帧属性集合
// 同时支持强类型属性和自定义扩展(不固定数据)
// ============================================================================
class FramePropertySet {
public:
FramePropertySet() = default;
// ------ 设置属性 ------
void set(FramePropertyKey key, FramePropertyValue value) {
properties_[key] = std::move(value);
}
void setCustom(const std::string& key, std::any value) {
customProperties_[key] = std::move(value);
}
// ------ 类型安全获取 ------
template<typename T>
std::optional<T> get(FramePropertyKey key) const {
auto it = properties_.find(key);
if (it == properties_.end()) return std::nullopt;
if (auto* val = std::get_if<T>(&it->second)) {
return *val;
}
return std::nullopt;
}
template<typename T>
T getOr(FramePropertyKey key, const T& defaultValue) const {
auto result = get<T>(key);
return result.value_or(defaultValue);
}
std::optional<std::any> getCustom(const std::string& key) const {
auto it = customProperties_.find(key);
if (it == customProperties_.end()) return std::nullopt;
return it->second;
}
// ------ 查询 ------
bool has(FramePropertyKey key) const {
return properties_.find(key) != properties_.end();
}
bool hasCustom(const std::string& key) const {
return customProperties_.find(key) != customProperties_.end();
}
bool empty() const {
return properties_.empty() && customProperties_.empty();
}
size_t count() const {
return properties_.size() + customProperties_.size();
}
// ------ 移除 ------
void remove(FramePropertyKey key) {
properties_.erase(key);
}
void removeCustom(const std::string& key) {
customProperties_.erase(key);
}
void clear() {
properties_.clear();
customProperties_.clear();
}
// ------ 迭代 ------
using PropertyMap = std::unordered_map<FramePropertyKey, FramePropertyValue, FramePropertyKeyHash>;
const PropertyMap& properties() const { return properties_; }
// ------ 链式 API ------
FramePropertySet& withSetFlag(int index) {
set(FramePropertyKey::SetFlag, index);
return *this;
}
FramePropertySet& withPlaySound(const std::string& path) {
set(FramePropertyKey::PlaySound, path);
return *this;
}
FramePropertySet& withImageRate(const Vec2& scale) {
set(FramePropertyKey::ImageRate, scale);
return *this;
}
FramePropertySet& withImageRotate(float degrees) {
set(FramePropertyKey::ImageRotate, degrees);
return *this;
}
FramePropertySet& withColorTint(const Color& color) {
set(FramePropertyKey::ColorTint, color);
return *this;
}
FramePropertySet& withInterpolation(bool enabled = true) {
set(FramePropertyKey::Interpolation, enabled);
return *this;
}
FramePropertySet& withBlendLinearDodge(bool enabled = true) {
set(FramePropertyKey::BlendLinearDodge, enabled);
return *this;
}
FramePropertySet& withLoop(bool enabled = true) {
set(FramePropertyKey::Loop, enabled);
return *this;
}
private:
PropertyMap properties_;
std::unordered_map<std::string, std::any> customProperties_;
};
} // namespace easy2d

View File

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

View File

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

View File

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

View File

@ -0,0 +1,169 @@
#pragma once
#include <easy2d/animation/sprite_frame.h>
#include <easy2d/graphics/texture_pool.h>
#include <unordered_map>
#include <mutex>
#include <string>
namespace easy2d {
// ============================================================================
// SpriteFrameCache - 精灵帧全局缓存(借鉴 Cocos SpriteFrameCache
// 全局单例管理所有精灵帧,避免重复创建,支持图集自动切割
// ============================================================================
class SpriteFrameCache {
public:
static SpriteFrameCache& getInstance() {
static SpriteFrameCache instance;
return instance;
}
// ------ 添加帧 ------
/// 添加单个精灵帧
void addSpriteFrame(Ptr<SpriteFrame> frame, const std::string& name) {
std::lock_guard<std::mutex> lock(mutex_);
frames_[name] = std::move(frame);
}
/// 从纹理和矩形区域创建并添加帧
void addSpriteFrameFromTexture(Ptr<Texture> texture, const Rect& rect,
const std::string& name) {
auto frame = SpriteFrame::create(std::move(texture), rect);
frame->setName(name);
addSpriteFrame(std::move(frame), name);
}
/// 从纹理图集批量切割添加(等宽等高网格)
void addSpriteFramesFromGrid(const std::string& texturePath,
int frameWidth, int frameHeight,
int frameCount = -1,
int spacing = 0, int margin = 0) {
auto texture = TexturePool::getInstance().get(texturePath);
if (!texture) return;
addSpriteFramesFromGrid(texture, texturePath, frameWidth, frameHeight,
frameCount, spacing, margin);
}
/// 从纹理对象批量切割添加(等宽等高网格,无需走 TexturePool
void addSpriteFramesFromGrid(Ptr<Texture> texture, const std::string& keyPrefix,
int frameWidth, int frameHeight,
int frameCount = -1,
int spacing = 0, int margin = 0) {
if (!texture) return;
int texW = texture->getWidth();
int texH = texture->getHeight();
int usableW = texW - 2 * margin;
int usableH = texH - 2 * margin;
int cols = (usableW + spacing) / (frameWidth + spacing);
int rows = (usableH + spacing) / (frameHeight + spacing);
int total = (frameCount > 0) ? frameCount : cols * rows;
std::lock_guard<std::mutex> lock(mutex_);
for (int i = 0; i < total; ++i) {
int col = i % cols;
int row = i / cols;
if (row >= rows) break;
Rect rect(
static_cast<float>(margin + col * (frameWidth + spacing)),
static_cast<float>(margin + row * (frameHeight + spacing)),
static_cast<float>(frameWidth),
static_cast<float>(frameHeight)
);
std::string name = keyPrefix + "#" + std::to_string(i);
auto frame = SpriteFrame::create(texture, rect);
frame->setName(name);
frames_[name] = std::move(frame);
}
}
// ------ 获取帧 ------
/// 按名称获取
Ptr<SpriteFrame> getSpriteFrame(const std::string& name) const {
std::lock_guard<std::mutex> lock(mutex_);
auto it = frames_.find(name);
if (it != frames_.end()) return it->second;
return nullptr;
}
/// 通过路径+索引获取或创建ANI 格式的定位方式)
Ptr<SpriteFrame> getOrCreateFromFile(const std::string& texturePath,
int index = 0) {
std::string key = texturePath + "#" + std::to_string(index);
{
std::lock_guard<std::mutex> lock(mutex_);
auto it = frames_.find(key);
if (it != frames_.end()) return it->second;
}
// 缓存未命中,从 TexturePool 加载纹理并创建 SpriteFrame
auto texture = TexturePool::getInstance().get(texturePath);
if (!texture) return nullptr;
// 默认整张纹理作为一帧index=0或用整张纹理
Rect rect(0.0f, 0.0f,
static_cast<float>(texture->getWidth()),
static_cast<float>(texture->getHeight()));
auto frame = SpriteFrame::create(texture, rect);
frame->setName(key);
std::lock_guard<std::mutex> lock(mutex_);
frames_[key] = frame;
return frame;
}
// ------ 缓存管理 ------
bool has(const std::string& name) const {
std::lock_guard<std::mutex> lock(mutex_);
return frames_.find(name) != frames_.end();
}
void removeSpriteFrame(const std::string& name) {
std::lock_guard<std::mutex> lock(mutex_);
frames_.erase(name);
}
/// 移除未被外部引用的精灵帧use_count == 1 表示仅缓存自身持有)
void removeUnusedSpriteFrames() {
std::lock_guard<std::mutex> lock(mutex_);
for (auto it = frames_.begin(); it != frames_.end(); ) {
if (it->second.use_count() == 1) {
it = frames_.erase(it);
} else {
++it;
}
}
}
void clear() {
std::lock_guard<std::mutex> lock(mutex_);
frames_.clear();
}
size_t count() const {
std::lock_guard<std::mutex> lock(mutex_);
return frames_.size();
}
private:
SpriteFrameCache() = default;
~SpriteFrameCache() = default;
SpriteFrameCache(const SpriteFrameCache&) = delete;
SpriteFrameCache& operator=(const SpriteFrameCache&) = delete;
mutable std::mutex mutex_;
std::unordered_map<std::string, Ptr<SpriteFrame>> frames_;
};
// 便捷宏
#define E2D_SPRITE_FRAME_CACHE() ::easy2d::SpriteFrameCache::getInstance()
} // namespace easy2d

View File

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

View File

@ -0,0 +1,47 @@
#pragma once
#include <memory>
#include <string>
#include <unordered_map>
#include <easy2d/core/types.h>
namespace easy2d {
class Sound;
class AudioEngine {
public:
static AudioEngine& getInstance();
AudioEngine(const AudioEngine&) = delete;
AudioEngine& operator=(const AudioEngine&) = delete;
AudioEngine(AudioEngine&&) = delete;
AudioEngine& operator=(AudioEngine&&) = delete;
bool initialize();
void shutdown();
std::shared_ptr<Sound> loadSound(const std::string& filePath);
std::shared_ptr<Sound> loadSound(const std::string& name, const std::string& filePath);
std::shared_ptr<Sound> getSound(const std::string& name);
void unloadSound(const std::string& name);
void unloadAllSounds();
void setMasterVolume(float volume);
float getMasterVolume() const;
void pauseAll();
void resumeAll();
void stopAll();
private:
AudioEngine() = default;
~AudioEngine();
std::unordered_map<std::string, std::shared_ptr<Sound>> sounds_;
float masterVolume_ = 1.0f;
bool initialized_ = false;
};
} // namespace easy2d

View File

@ -0,0 +1,57 @@
#pragma once
#include <string>
#include <easy2d/core/types.h>
struct Mix_Chunk;
namespace easy2d {
class AudioEngine;
class Sound {
public:
~Sound();
Sound(const Sound&) = delete;
Sound& operator=(const Sound&) = delete;
bool play();
void pause();
void resume();
void stop();
bool isPlaying() const;
bool isPaused() const;
void setVolume(float volume);
float getVolume() const { return volume_; }
void setLooping(bool looping);
bool isLooping() const { return looping_; }
void setPitch(float pitch);
float getPitch() const { return pitch_; }
float getDuration() const;
float getCursor() const;
void setCursor(float seconds);
const std::string& getFilePath() const { return filePath_; }
const std::string& getName() const { return name_; }
private:
friend class AudioEngine;
Sound(const std::string& name, const std::string& filePath, Mix_Chunk* chunk);
std::string name_;
std::string filePath_;
Mix_Chunk* chunk_ = nullptr;
int channel_ = -1; // SDL_mixer 分配的通道,-1 表示未播放
float volume_ = 1.0f;
float pitch_ = 1.0f;
bool looping_ = false;
};
} // namespace easy2d

View File

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

View File

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

View File

@ -0,0 +1,507 @@
#pragma once
#include <easy2d/core/types.h>
#include <string>
#include <vector>
#include <cstring>
namespace easy2d {
// ============================================================================
// String 类 - 跨平台字符串,内部统一使用 UTF-8 存储
// ============================================================================
class String {
public:
// ------------------------------------------------------------------------
// 构造函数
// ------------------------------------------------------------------------
String() = default;
String(const char* utf8) : data_(utf8 ? utf8 : "") {}
String(const std::string& utf8) : data_(utf8) {}
explicit String(const wchar_t* wide);
explicit String(const std::wstring& wide);
explicit String(const char16_t* utf16);
explicit String(const std::u16string& utf16);
explicit String(const char32_t* utf32);
explicit String(const std::u32string& utf32);
// ------------------------------------------------------------------------
// 静态工厂方法
// ------------------------------------------------------------------------
static String fromUtf8(const char* utf8);
static String fromUtf8(const std::string& utf8);
static String fromWide(const wchar_t* wide);
static String fromWide(const std::wstring& wide);
static String fromUtf16(const char16_t* utf16);
static String fromUtf16(const std::u16string& utf16);
static String fromUtf32(const char32_t* utf32);
static String fromUtf32(const std::u32string& utf32);
/// 从 GBK/GB2312 编码构造Windows 中文系统常用)
static String fromGBK(const char* gbk);
static String fromGBK(const std::string& gbk);
// ------------------------------------------------------------------------
// 编码转换
// ------------------------------------------------------------------------
const std::string& toUtf8() const { return data_; }
std::string toUtf8String() const { return data_; }
std::wstring toWide() const;
std::u16string toUtf16() const;
std::u32string toUtf32() const;
/// 转换为 GBK/GB2312 编码Windows 中文系统常用)
std::string toGBK() const;
// ------------------------------------------------------------------------
// 基础操作
// ------------------------------------------------------------------------
/// 返回 Unicode 字符数(不是字节数)
size_t length() const;
/// 返回 UTF-8 字节数
size_t byteSize() const { return data_.size(); }
bool empty() const { return data_.empty(); }
const char* c_str() const { return data_.c_str(); }
const std::string& str() const { return data_; }
std::string& str() { return data_; }
// ------------------------------------------------------------------------
// 字符串操作
// ------------------------------------------------------------------------
void clear() { data_.clear(); }
String& append(const String& other);
String& append(const char* utf8);
String substring(size_t start, size_t len = npos) const;
/// 查找子串,返回 Unicode 字符索引,未找到返回 npos
size_t find(const String& substr, size_t start = 0) const;
/// 是否以指定字符串开头
bool startsWith(const String& prefix) const;
/// 是否以指定字符串结尾
bool endsWith(const String& suffix) const;
/// 去除首尾空白字符
String trim() const;
/// 分割字符串
std::vector<String> split(const String& delimiter) const;
/// 替换所有匹配子串
String replaceAll(const String& from, const String& to) const;
// ------------------------------------------------------------------------
// 运算符重载
// ------------------------------------------------------------------------
String operator+(const String& other) const;
String& operator+=(const String& other);
bool operator==(const String& other) const { return data_ == other.data_; }
bool operator!=(const String& other) const { return data_ != other.data_; }
bool operator<(const String& other) const { return data_ < other.data_; }
bool operator>(const String& other) const { return data_ > other.data_; }
char operator[](size_t index) const { return data_[index]; }
// ------------------------------------------------------------------------
// 迭代器支持
// ------------------------------------------------------------------------
auto begin() { return data_.begin(); }
auto end() { return data_.end(); }
auto begin() const { return data_.begin(); }
auto end() const { return data_.end(); }
auto cbegin() const { return data_.cbegin(); }
auto cend() const { return data_.cend(); }
// ------------------------------------------------------------------------
// 静态常量
// ------------------------------------------------------------------------
static constexpr size_t npos = static_cast<size_t>(-1);
// ------------------------------------------------------------------------
// 格式化字符串(类似 sprintf
// ------------------------------------------------------------------------
template<typename... Args>
static String format(const char* fmt, Args&&... args);
private:
std::string data_; // 内部使用 UTF-8 存储
// UTF-8 辅助函数
static size_t utf8Length(const std::string& str);
static std::string utf8Substring(const std::string& str, size_t start, size_t len);
static size_t utf8CharIndexToByteIndex(const std::string& str, size_t charIndex);
};
// ============================================================================
// 内联实现
// ============================================================================
inline String::String(const wchar_t* wide) {
if (wide) {
std::wstring wstr(wide);
*this = fromWide(wstr);
}
}
inline String::String(const std::wstring& wide) {
*this = fromWide(wide);
}
inline String::String(const char16_t* utf16) {
if (utf16) {
std::u16string u16str(utf16);
*this = fromUtf16(u16str);
}
}
inline String::String(const std::u16string& utf16) {
*this = fromUtf16(utf16);
}
inline String::String(const char32_t* utf32) {
if (utf32) {
std::u32string u32str(utf32);
*this = fromUtf32(u32str);
}
}
inline String::String(const std::u32string& utf32) {
*this = fromUtf32(utf32);
}
// 静态工厂方法
inline String String::fromUtf8(const char* utf8) {
return String(utf8 ? utf8 : "");
}
inline String String::fromUtf8(const std::string& utf8) {
return String(utf8);
}
// 编码转换实现
inline std::wstring String::toWide() const {
if (data_.empty()) return std::wstring();
if constexpr (sizeof(wchar_t) == 4) {
// wchar_t is 32-bit (Linux/Switch): same as UTF-32
std::u32string u32 = toUtf32();
return std::wstring(u32.begin(), u32.end());
} else {
// wchar_t is 16-bit (Windows): same as UTF-16
std::u16string u16 = toUtf16();
return std::wstring(u16.begin(), u16.end());
}
}
inline String String::fromWide(const std::wstring& wide) {
if (wide.empty()) return String();
if constexpr (sizeof(wchar_t) == 4) {
std::u32string u32(wide.begin(), wide.end());
return fromUtf32(u32);
} else {
std::u16string u16(wide.begin(), wide.end());
return fromUtf16(u16);
}
}
inline String String::fromWide(const wchar_t* wide) {
return wide ? fromWide(std::wstring(wide)) : String();
}
inline std::u16string String::toUtf16() const {
if (data_.empty()) return std::u16string();
// UTF-8 → UTF-32 → UTF-16 (with surrogate pairs)
std::u32string u32 = toUtf32();
std::u16string result;
result.reserve(u32.size());
for (char32_t ch : u32) {
if (ch <= 0xFFFF) {
result.push_back(static_cast<char16_t>(ch));
} else if (ch <= 0x10FFFF) {
// Surrogate pair
ch -= 0x10000;
result.push_back(static_cast<char16_t>(0xD800 | (ch >> 10)));
result.push_back(static_cast<char16_t>(0xDC00 | (ch & 0x3FF)));
}
}
return result;
}
inline String String::fromUtf16(const std::u16string& utf16) {
if (utf16.empty()) return String();
// UTF-16 → UTF-32 → UTF-8
std::u32string u32;
u32.reserve(utf16.size());
for (size_t i = 0; i < utf16.size(); ++i) {
char16_t cu = utf16[i];
char32_t ch;
if (cu >= 0xD800 && cu <= 0xDBFF && i + 1 < utf16.size()) {
// High surrogate
char16_t cl = utf16[i + 1];
if (cl >= 0xDC00 && cl <= 0xDFFF) {
ch = 0x10000 + ((static_cast<char32_t>(cu - 0xD800) << 10) |
(cl - 0xDC00));
++i;
} else {
ch = cu; // Invalid, pass through
}
} else {
ch = cu;
}
u32.push_back(ch);
}
return fromUtf32(u32);
}
inline String String::fromUtf16(const char16_t* utf16) {
return utf16 ? fromUtf16(std::u16string(utf16)) : String();
}
inline std::u32string String::toUtf32() const {
std::u32string result;
result.reserve(length());
const char* ptr = data_.c_str();
const char* end = ptr + data_.size();
while (ptr < end) {
char32_t ch = 0;
unsigned char byte = static_cast<unsigned char>(*ptr);
if ((byte & 0x80) == 0) {
// 1-byte sequence
ch = byte;
ptr += 1;
} else if ((byte & 0xE0) == 0xC0) {
// 2-byte sequence
ch = (byte & 0x1F) << 6;
ch |= (static_cast<unsigned char>(ptr[1]) & 0x3F);
ptr += 2;
} else if ((byte & 0xF0) == 0xE0) {
// 3-byte sequence
ch = (byte & 0x0F) << 12;
ch |= (static_cast<unsigned char>(ptr[1]) & 0x3F) << 6;
ch |= (static_cast<unsigned char>(ptr[2]) & 0x3F);
ptr += 3;
} else if ((byte & 0xF8) == 0xF0) {
// 4-byte sequence
ch = (byte & 0x07) << 18;
ch |= (static_cast<unsigned char>(ptr[1]) & 0x3F) << 12;
ch |= (static_cast<unsigned char>(ptr[2]) & 0x3F) << 6;
ch |= (static_cast<unsigned char>(ptr[3]) & 0x3F);
ptr += 4;
} else {
// Invalid UTF-8, skip
ptr += 1;
continue;
}
result.push_back(ch);
}
return result;
}
inline String String::fromUtf32(const std::u32string& utf32) {
std::string result;
for (char32_t ch : utf32) {
if (ch <= 0x7F) {
// 1-byte
result.push_back(static_cast<char>(ch));
} else if (ch <= 0x7FF) {
// 2-byte
result.push_back(static_cast<char>(0xC0 | ((ch >> 6) & 0x1F)));
result.push_back(static_cast<char>(0x80 | (ch & 0x3F)));
} else if (ch <= 0xFFFF) {
// 3-byte
result.push_back(static_cast<char>(0xE0 | ((ch >> 12) & 0x0F)));
result.push_back(static_cast<char>(0x80 | ((ch >> 6) & 0x3F)));
result.push_back(static_cast<char>(0x80 | (ch & 0x3F)));
} else if (ch <= 0x10FFFF) {
// 4-byte
result.push_back(static_cast<char>(0xF0 | ((ch >> 18) & 0x07)));
result.push_back(static_cast<char>(0x80 | ((ch >> 12) & 0x3F)));
result.push_back(static_cast<char>(0x80 | ((ch >> 6) & 0x3F)));
result.push_back(static_cast<char>(0x80 | (ch & 0x3F)));
}
}
return String(result);
}
inline String String::fromUtf32(const char32_t* utf32) {
return utf32 ? fromUtf32(std::u32string(utf32)) : String();
}
// UTF-8 辅助函数
inline size_t String::utf8Length(const std::string& str) {
size_t len = 0;
for (unsigned char c : str) {
if ((c & 0xC0) != 0x80) {
++len;
}
}
return len;
}
inline size_t String::length() const {
return utf8Length(data_);
}
inline size_t String::utf8CharIndexToByteIndex(const std::string& str, size_t charIndex) {
size_t charCount = 0;
size_t byteIndex = 0;
while (byteIndex < str.size() && charCount < charIndex) {
unsigned char c = static_cast<unsigned char>(str[byteIndex]);
if ((c & 0xC0) != 0x80) {
++charCount;
}
++byteIndex;
}
return byteIndex;
}
inline std::string String::utf8Substring(const std::string& str, size_t start, size_t len) {
size_t startByte = utf8CharIndexToByteIndex(str, start);
size_t endByte = (len == npos) ? str.size() : utf8CharIndexToByteIndex(str, start + len);
return str.substr(startByte, endByte - startByte);
}
inline String String::substring(size_t start, size_t len) const {
return String(utf8Substring(data_, start, len));
}
// 字符串操作
inline String& String::append(const String& other) {
data_.append(other.data_);
return *this;
}
inline String& String::append(const char* utf8) {
if (utf8) data_.append(utf8);
return *this;
}
inline size_t String::find(const String& substr, size_t start) const {
if (substr.empty() || start >= length()) return npos;
size_t startByte = utf8CharIndexToByteIndex(data_, start);
size_t pos = data_.find(substr.data_, startByte);
if (pos == std::string::npos) return npos;
// 转换字节索引到字符索引
return utf8Length(data_.substr(0, pos));
}
inline bool String::startsWith(const String& prefix) const {
if (prefix.data_.size() > data_.size()) return false;
return data_.compare(0, prefix.data_.size(), prefix.data_) == 0;
}
inline bool String::endsWith(const String& suffix) const {
if (suffix.data_.size() > data_.size()) return false;
return data_.compare(data_.size() - suffix.data_.size(), suffix.data_.size(), suffix.data_) == 0;
}
inline String String::trim() const {
size_t start = 0;
while (start < data_.size() && std::isspace(static_cast<unsigned char>(data_[start]))) {
++start;
}
size_t end = data_.size();
while (end > start && std::isspace(static_cast<unsigned char>(data_[end - 1]))) {
--end;
}
return String(data_.substr(start, end - start));
}
inline std::vector<String> String::split(const String& delimiter) const {
std::vector<String> result;
if (delimiter.empty()) {
result.push_back(*this);
return result;
}
size_t start = 0;
size_t end = data_.find(delimiter.data_, start);
while (end != std::string::npos) {
result.push_back(String(data_.substr(start, end - start)));
start = end + delimiter.data_.size();
end = data_.find(delimiter.data_, start);
}
result.push_back(String(data_.substr(start)));
return result;
}
inline String String::replaceAll(const String& from, const String& to) const {
if (from.empty()) return *this;
std::string result = data_;
size_t pos = 0;
while ((pos = result.find(from.data_, pos)) != std::string::npos) {
result.replace(pos, from.data_.size(), to.data_);
pos += to.data_.size();
}
return String(result);
}
// 运算符重载
inline String String::operator+(const String& other) const {
String result(*this);
result.append(other);
return result;
}
inline String& String::operator+=(const String& other) {
return append(other);
}
// 格式化字符串
#include <cstdio>
template<typename... Args>
String String::format(const char* fmt, Args&&... args) {
int size = std::snprintf(nullptr, 0, fmt, std::forward<Args>(args)...);
if (size <= 0) return String();
std::string result(size, '\0');
std::snprintf(&result[0], size + 1, fmt, std::forward<Args>(args)...);
return String(result);
}
// 全局运算符
inline String operator+(const char* lhs, const String& rhs) {
return String(lhs) + rhs;
}
} // namespace easy2d

View File

@ -0,0 +1,51 @@
#pragma once
#include <cstdint>
#include <functional>
#include <memory>
namespace easy2d {
// ---------------------------------------------------------------------------
// 智能指针别名
// ---------------------------------------------------------------------------
template<typename T>
using Ptr = std::shared_ptr<T>;
template<typename T>
using UniquePtr = std::unique_ptr<T>;
template<typename T>
using WeakPtr = std::weak_ptr<T>;
/// 创建 shared_ptr 的便捷函数
template<typename T, typename... Args>
inline Ptr<T> makePtr(Args&&... args) {
return std::make_shared<T>(std::forward<Args>(args)...);
}
/// 创建 unique_ptr 的便捷函数
template<typename T, typename... Args>
inline UniquePtr<T> makeUnique(Args&&... args) {
return std::make_unique<T>(std::forward<Args>(args)...);
}
// ---------------------------------------------------------------------------
// 函数别名
// ---------------------------------------------------------------------------
template<typename Sig>
using Function = std::function<Sig>;
// ---------------------------------------------------------------------------
// 基础类型别名
// ---------------------------------------------------------------------------
using int8 = std::int8_t;
using int16 = std::int16_t;
using int32 = std::int32_t;
using int64 = std::int64_t;
using uint8 = std::uint8_t;
using uint16 = std::uint16_t;
using uint32 = std::uint32_t;
using uint64 = std::uint64_t;
} // namespace easy2d

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,76 @@
#pragma once
#include <easy2d/graphics/render_backend.h>
#include <easy2d/graphics/opengl/gl_shader.h>
#include <easy2d/graphics/opengl/gl_sprite_batch.h>
// 使用标准 GLES3.2
#include <GLES3/gl32.h>
namespace easy2d {
class Window;
// ============================================================================
// OpenGL 渲染器实现
// ============================================================================
class GLRenderer : public RenderBackend {
public:
GLRenderer();
~GLRenderer() override;
// RenderBackend 接口实现
bool init(Window* window) override;
void shutdown() override;
void beginFrame(const Color& clearColor) override;
void endFrame() override;
void setViewport(int x, int y, int width, int height) override;
void setVSync(bool enabled) override;
void setBlendMode(BlendMode mode) override;
void setViewProjection(const glm::mat4& matrix) override;
Ptr<Texture> createTexture(int width, int height, const uint8_t* pixels, int channels) override;
Ptr<Texture> loadTexture(const std::string& filepath) override;
void beginSpriteBatch() override;
void drawSprite(const Texture& texture, const Rect& destRect, const Rect& srcRect,
const Color& tint, float rotation, const Vec2& anchor) override;
void drawSprite(const Texture& texture, const Vec2& position, const Color& tint) override;
void endSpriteBatch() override;
void drawLine(const Vec2& start, const Vec2& end, const Color& color, float width) override;
void drawRect(const Rect& rect, const Color& color, float width) override;
void fillRect(const Rect& rect, const Color& color) override;
void drawCircle(const Vec2& center, float radius, const Color& color, int segments, float width) override;
void fillCircle(const Vec2& center, float radius, const Color& color, int segments) override;
void drawTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3, const Color& color, float width) override;
void fillTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3, const Color& color) override;
void drawPolygon(const std::vector<Vec2>& points, const Color& color, float width) override;
void fillPolygon(const std::vector<Vec2>& points, const Color& color) override;
Ptr<FontAtlas> createFontAtlas(const std::string& filepath, int fontSize, bool useSDF = false) override;
void drawText(const FontAtlas& font, const String& text, const Vec2& position, const Color& color) override;
void drawText(const FontAtlas& font, const String& text, float x, float y, const Color& color) override;
Stats getStats() const override { return stats_; }
void resetStats() override;
private:
Window* window_;
GLSpriteBatch spriteBatch_;
GLShader shapeShader_;
GLuint shapeVao_;
GLuint shapeVbo_;
glm::mat4 viewProjection_;
Stats stats_;
bool vsync_;
void initShapeRendering();
void setupBlendMode(BlendMode mode);
};
} // namespace easy2d

View File

@ -0,0 +1,56 @@
#pragma once
// 使用标准 GLES3.2
#include <GLES3/gl32.h>
#include <string>
#include <unordered_map>
#include <glm/mat4x4.hpp>
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
#include <glm/vec4.hpp>
namespace easy2d {
// ============================================================================
// OpenGL Shader 程序
// ============================================================================
class GLShader {
public:
GLShader();
~GLShader();
// 从源码编译
bool compileFromSource(const char* vertexSource, const char* fragmentSource);
// 从文件加载并编译
bool compileFromFile(const std::string& vertexPath, const std::string& fragmentPath);
// 使用/激活
void bind() const;
void unbind() const;
// Uniform 设置
void setBool(const std::string& name, bool value);
void setInt(const std::string& name, int value);
void setFloat(const std::string& name, float value);
void setVec2(const std::string& name, const glm::vec2& value);
void setVec3(const std::string& name, const glm::vec3& value);
void setVec4(const std::string& name, const glm::vec4& value);
void setMat4(const std::string& name, const glm::mat4& value);
// 获取程序 ID
GLuint getProgramID() const { return programID_; }
// 检查是否有效
bool isValid() const { return programID_ != 0; }
private:
GLuint programID_;
std::unordered_map<std::string, GLint> uniformCache_;
GLuint compileShader(GLenum type, const char* source);
GLint getUniformLocation(const std::string& name);
};
} // namespace easy2d

View File

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

View File

@ -0,0 +1,72 @@
#pragma once
#include <easy2d/graphics/texture.h>
#include <easy2d/graphics/alpha_mask.h>
// 使用标准 GLES3.2
#include <GLES3/gl32.h>
#include <memory>
namespace easy2d {
// ============================================================================
// OpenGL 纹理实现
// ============================================================================
class GLTexture : public Texture {
public:
GLTexture(int width, int height, const uint8_t* pixels, int channels);
GLTexture(const std::string& filepath);
~GLTexture();
// Texture 接口实现
int getWidth() const override { return width_; }
int getHeight() const override { return height_; }
Size getSize() const override { return Size(static_cast<float>(width_), static_cast<float>(height_)); }
int getChannels() const override { return channels_; }
PixelFormat getFormat() const override;
void* getNativeHandle() const override { return reinterpret_cast<void*>(static_cast<uintptr_t>(textureID_)); }
bool isValid() const override { return textureID_ != 0; }
void setFilter(bool linear) override;
void setWrap(bool repeat) override;
// 从参数创建纹理的工厂方法
static Ptr<Texture> create(int width, int height, PixelFormat format);
// 加载压缩纹理KTX/DDS 格式)
bool loadCompressed(const std::string& filepath);
// OpenGL 特定
GLuint getTextureID() const { return textureID_; }
void bind(unsigned int slot = 0) const;
void unbind() const;
// 获取纹理数据大小(字节),用于 VRAM 跟踪
size_t getDataSize() const { return dataSize_; }
// Alpha 遮罩
bool hasAlphaMask() const { return alphaMask_ != nullptr && alphaMask_->isValid(); }
const AlphaMask* getAlphaMask() const { return alphaMask_.get(); }
void generateAlphaMask(); // 从当前纹理数据生成遮罩
private:
GLuint textureID_;
int width_;
int height_;
int channels_;
PixelFormat format_;
size_t dataSize_;
// 原始像素数据(用于生成遮罩)
std::vector<uint8_t> pixelData_;
std::unique_ptr<AlphaMask> alphaMask_;
void createTexture(const uint8_t* pixels);
// KTX 文件加载
bool loadKTX(const std::string& filepath);
// DDS 文件加载
bool loadDDS(const std::string& filepath);
};
} // namespace easy2d

View File

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

View File

@ -0,0 +1,128 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/core/math_types.h>
#include <easy2d/core/color.h>
#include <easy2d/core/string.h>
#include <variant>
#include <vector>
namespace easy2d {
// 前向声明
class Texture;
class FontAtlas;
// ============================================================================
// 渲染命令类型
// ============================================================================
enum class RenderCommandType {
Sprite,
Line,
Rect,
FilledRect,
Circle,
FilledCircle,
Triangle,
FilledTriangle,
Polygon,
FilledPolygon,
Text
};
// ============================================================================
// 精灵数据
// ============================================================================
struct SpriteData {
Ptr<Texture> texture;
Rect destRect;
Rect srcRect;
Color tint;
float rotation;
Vec2 anchor;
};
// ============================================================================
// 直线数据
// ============================================================================
struct LineData {
Vec2 start;
Vec2 end;
Color color;
float width;
};
// ============================================================================
// 矩形数据
// ============================================================================
struct RectData {
Rect rect;
Color color;
float width;
};
// ============================================================================
// 圆形数据
// ============================================================================
struct CircleData {
Vec2 center;
float radius;
Color color;
int segments;
float width;
};
// ============================================================================
// 三角形数据
// ============================================================================
struct TriangleData {
Vec2 p1;
Vec2 p2;
Vec2 p3;
Color color;
float width;
};
// ============================================================================
// 多边形数据
// ============================================================================
struct PolygonData {
std::vector<Vec2> points;
Color color;
float width;
};
// ============================================================================
// 文字数据
// ============================================================================
struct TextData {
Ptr<FontAtlas> font;
String text;
Vec2 position;
Color color;
};
// ============================================================================
// 渲染命令
// ============================================================================
struct RenderCommand {
RenderCommandType type;
int zOrder;
std::variant<
SpriteData,
LineData,
RectData,
CircleData,
TriangleData,
PolygonData,
TextData
> data;
// 用于排序
bool operator<(const RenderCommand& other) const {
return zOrder < other.zOrder;
}
};
} // namespace easy2d

View File

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

View File

@ -0,0 +1,319 @@
#pragma once
#include <easy2d/graphics/opengl/gl_shader.h>
#include <easy2d/core/types.h>
#include <easy2d/core/color.h>
#include <glm/vec4.hpp>
namespace easy2d {
struct WaterParams {
float waveSpeed = 1.0f;
float waveAmplitude = 0.02f;
float waveFrequency = 4.0f;
};
struct OutlineParams {
Color color = Colors::Black;
float thickness = 2.0f;
};
struct DistortionParams {
float distortionAmount = 0.02f;
float timeScale = 1.0f;
};
struct PixelateParams {
float pixelSize = 8.0f;
};
struct InvertParams {
float strength = 1.0f;
};
struct GrayscaleParams {
float intensity = 1.0f;
};
struct BlurParams {
float radius = 5.0f;
};
namespace ShaderSource {
static const char* StandardVert = 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* StandardFrag = 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;
}
}
)";
static const char* WaterFrag = R"(
#version 300 es
precision highp float;
in vec2 v_texCoord;
in vec4 v_color;
uniform sampler2D u_texture;
uniform float u_waveSpeed;
uniform float u_waveAmplitude;
uniform float u_waveFrequency;
uniform float u_time;
out vec4 fragColor;
void main() {
vec2 uv = v_texCoord;
// 水波纹效果
float wave = sin(uv.y * u_waveFrequency + u_time * u_waveSpeed) * u_waveAmplitude;
uv.x += wave;
vec4 texColor = texture(u_texture, uv);
fragColor = texColor * v_color;
if (fragColor.a < 0.01) {
discard;
}
}
)";
static const char* OutlineFrag = R"(
#version 300 es
precision highp float;
in vec2 v_texCoord;
in vec4 v_color;
uniform sampler2D u_texture;
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 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;
}
if (fragColor.a < 0.01) {
discard;
}
}
)";
static const char* DistortionFrag = R"(
#version 300 es
precision highp float;
in vec2 v_texCoord;
in vec4 v_color;
uniform sampler2D u_texture;
uniform float u_distortionAmount;
uniform float u_time;
uniform float u_timeScale;
out vec4 fragColor;
void main() {
vec2 uv = v_texCoord;
// 扭曲效果
float t = u_time * u_timeScale;
float dx = sin(uv.y * 10.0 + t) * u_distortionAmount;
float dy = cos(uv.x * 10.0 + t) * u_distortionAmount;
uv += vec2(dx, dy);
vec4 texColor = texture(u_texture, uv);
fragColor = texColor * v_color;
if (fragColor.a < 0.01) {
discard;
}
}
)";
static const char* PixelateFrag = R"(
#version 300 es
precision highp float;
in vec2 v_texCoord;
in vec4 v_color;
uniform sampler2D u_texture;
uniform float u_pixelSize;
uniform vec2 u_textureSize;
uniform float u_opacity;
out vec4 fragColor;
void main() {
vec2 pixel = u_pixelSize / u_textureSize;
vec2 uv = floor(v_texCoord / pixel) * pixel + pixel * 0.5;
vec4 texColor = texture(u_texture, uv);
fragColor = texColor * v_color;
fragColor.a *= u_opacity;
if (fragColor.a < 0.01) {
discard;
}
}
)";
static const char* InvertFrag = R"(
#version 300 es
precision highp float;
in vec2 v_texCoord;
in vec4 v_color;
uniform sampler2D u_texture;
uniform float u_strength;
uniform float u_opacity;
out vec4 fragColor;
void main() {
vec4 texColor = texture(u_texture, v_texCoord) * v_color;
vec3 inverted = vec3(1.0) - texColor.rgb;
texColor.rgb = mix(texColor.rgb, inverted, u_strength);
fragColor = texColor;
fragColor.a *= u_opacity;
if (fragColor.a < 0.01) {
discard;
}
}
)";
static const char* GrayscaleFrag = R"(
#version 300 es
precision highp float;
in vec2 v_texCoord;
in vec4 v_color;
uniform sampler2D u_texture;
uniform float u_intensity;
uniform float u_opacity;
out vec4 fragColor;
void main() {
vec4 texColor = texture(u_texture, v_texCoord) * v_color;
float gray = dot(texColor.rgb, vec3(0.299, 0.587, 0.114));
texColor.rgb = mix(texColor.rgb, vec3(gray), u_intensity);
fragColor = texColor;
fragColor.a *= u_opacity;
if (fragColor.a < 0.01) {
discard;
}
}
)";
static const char* BlurFrag = R"(
#version 300 es
precision highp float;
in vec2 v_texCoord;
in vec4 v_color;
uniform sampler2D u_texture;
uniform float u_radius;
uniform vec2 u_textureSize;
uniform float u_opacity;
out vec4 fragColor;
void main() {
vec2 texel = u_radius / u_textureSize;
vec4 sum = vec4(0.0);
sum += texture(u_texture, v_texCoord + texel * vec2(-1.0, -1.0));
sum += texture(u_texture, v_texCoord + texel * vec2( 0.0, -1.0));
sum += texture(u_texture, v_texCoord + texel * vec2( 1.0, -1.0));
sum += texture(u_texture, v_texCoord + texel * vec2(-1.0, 0.0));
sum += texture(u_texture, v_texCoord + texel * vec2( 0.0, 0.0));
sum += texture(u_texture, v_texCoord + texel * vec2( 1.0, 0.0));
sum += texture(u_texture, v_texCoord + texel * vec2(-1.0, 1.0));
sum += texture(u_texture, v_texCoord + texel * vec2( 0.0, 1.0));
sum += texture(u_texture, v_texCoord + texel * vec2( 1.0, 1.0));
vec4 texColor = sum / 9.0;
fragColor = texColor * v_color;
fragColor.a *= u_opacity;
if (fragColor.a < 0.01) {
discard;
}
}
)";
} // namespace ShaderSource
class ShaderPreset {
public:
static Ptr<GLShader> Water(const WaterParams& params);
static Ptr<GLShader> Outline(const OutlineParams& params);
static Ptr<GLShader> Distortion(const DistortionParams& params);
static Ptr<GLShader> Pixelate(const PixelateParams& params);
static Ptr<GLShader> Invert(const InvertParams& params);
static Ptr<GLShader> Grayscale(const GrayscaleParams& params);
static Ptr<GLShader> Blur(const BlurParams& params);
static Ptr<GLShader> GrayscaleOutline(const GrayscaleParams& grayParams,
const OutlineParams& outlineParams);
static Ptr<GLShader> PixelateInvert(const PixelateParams& pixParams,
const InvertParams& invParams);
};
} // namespace easy2d

View File

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

View File

@ -0,0 +1,64 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/core/math_types.h>
namespace easy2d {
// ============================================================================
// 像素格式枚举
// ============================================================================
enum class PixelFormat {
R8, // 单通道灰度
RG8, // 双通道
RGB8, // RGB 24位
RGBA8, // RGBA 32位默认
RGB16F, // RGB 半精度浮点
RGBA16F, // RGBA 半精度浮点
RGB32F, // RGB 全精度浮点
RGBA32F, // RGBA 全精度浮点
Depth16, // 16位深度
Depth24, // 24位深度
Depth32F, // 32位浮点深度
Depth24Stencil8, // 24位深度 + 8位模板
// 压缩纹理格式
ETC2_RGB8, // ETC2 RGB 压缩
ETC2_RGBA8, // ETC2 RGBA 压缩
ASTC_4x4, // ASTC 4x4 压缩
ASTC_6x6, // ASTC 6x6 压缩
ASTC_8x8 // ASTC 8x8 压缩
};
// ============================================================================
// 纹理接口
// ============================================================================
class Texture {
public:
virtual ~Texture() = default;
// 获取尺寸
virtual int getWidth() const = 0;
virtual int getHeight() const = 0;
virtual Size getSize() const = 0;
// 获取通道数
virtual int getChannels() const = 0;
// 获取像素格式
virtual PixelFormat getFormat() const = 0;
// 获取原始句柄(用于底层渲染)
virtual void* getNativeHandle() const = 0;
// 是否有效
virtual bool isValid() const = 0;
// 设置过滤模式
virtual void setFilter(bool linear) = 0;
// 设置环绕模式
virtual void setWrap(bool repeat) = 0;
};
} // namespace easy2d

View File

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

View File

@ -0,0 +1,62 @@
#pragma once
#include <cstddef>
#include <cstdint>
#include <mutex>
namespace easy2d {
// ============================================================================
// VRAM 管理器 - 跟踪显存使用情况
// ============================================================================
class VRAMManager {
public:
static VRAMManager& getInstance();
// 纹理显存跟踪
void allocTexture(size_t size);
void freeTexture(size_t size);
// VBO/FBO 显存跟踪
void allocBuffer(size_t size);
void freeBuffer(size_t size);
// 查询显存使用情况
size_t getUsedVRAM() const;
size_t getTextureVRAM() const;
size_t getBufferVRAM() const;
size_t getAvailableVRAM() const;
// 显存预算管理
void setVRAMBudget(size_t budget);
size_t getVRAMBudget() const;
bool isOverBudget() const;
// 统计信息
void printStats() const;
// 重置计数器
void reset();
private:
VRAMManager();
~VRAMManager() = default;
VRAMManager(const VRAMManager&) = delete;
VRAMManager& operator=(const VRAMManager&) = delete;
mutable std::mutex mutex_;
size_t textureVRAM_;
size_t bufferVRAM_;
size_t vramBudget_;
// 统计
uint32_t textureAllocCount_;
uint32_t textureFreeCount_;
uint32_t bufferAllocCount_;
uint32_t bufferFreeCount_;
size_t peakTextureVRAM_;
size_t peakBufferVRAM_;
};
} // namespace easy2d

View File

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

View File

@ -0,0 +1,79 @@
#pragma once
/**
* @file switch_compat.h
* @brief Nintendo Switch
*
* Switch
*/
#ifdef __SWITCH__
// ============================================================================
// Switch 平台包含
// ============================================================================
#include <switch.h>
#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <string>
// ============================================================================
// Switch 特定的内存操作
// ============================================================================
// 不需要特殊处理libnx已提供malloc/free
// ============================================================================
// Switch 文件系统相关
// ============================================================================
// RomFS路径前缀
#define SWITCH_ROMFS_PREFIX "romfs:/"
// RomFS 根路径常量
namespace easy2d {
namespace romfs {
static constexpr const char* ROMFS_ROOT = "romfs:/";
// 检查文件是否存在于 romfs 中
inline bool fileExists(const char* path) {
struct stat st;
return stat(path, &st) == 0;
}
// 检查路径是否为 romfs 路径
inline bool isRomfsPath(const char* path) {
return path && (strncmp(path, "romfs:/", 7) == 0 || strncmp(path, "romfs:\\", 7) == 0);
}
// 构建 romfs 完整路径
inline std::string makePath(const char* relativePath) {
std::string result = ROMFS_ROOT;
result += relativePath;
return result;
}
} // namespace romfs
} // namespace easy2d
// ============================================================================
// Switch 调试输出
// ============================================================================
#ifdef E2D_DEBUG
#define SWITCH_DEBUG_PRINTF(fmt, ...) printf("[Easy2D] " fmt "\n", ##__VA_ARGS__)
#else
#define SWITCH_DEBUG_PRINTF(fmt, ...) ((void)0)
#endif
// ============================================================================
// Switch 特定的编译器属性
// ============================================================================
#define SWITCH_LIKELY(x) __builtin_expect(!!(x), 1)
#define SWITCH_UNLIKELY(x) __builtin_expect(!!(x), 0)
// ============================================================================
// Switch 特定的平台检查宏
// ============================================================================
#define IS_SWITCH_PLATFORM 1
#endif // __SWITCH__

View File

@ -0,0 +1,138 @@
#pragma once
#include <easy2d/core/types.h>
#include <easy2d/core/string.h>
#include <easy2d/core/math_types.h>
#include <functional>
#include <SDL.h>
namespace easy2d {
// 前向声明
class EventQueue;
class Input;
// ============================================================================
// 窗口配置
// ============================================================================
struct WindowConfig {
String title = "Easy2D Application";
int width = 1280;
int height = 720;
bool fullscreen = true; // Switch 始终全屏
bool resizable = false;
bool vsync = true;
int msaaSamples = 0;
bool centerWindow = false;
};
// ============================================================================
// 鼠标光标形状枚举 (保留接口兼容性Switch 上无效)
// ============================================================================
enum class CursorShape {
Arrow,
IBeam,
Crosshair,
Hand,
HResize,
VResize,
ResizeAll,
ResizeNWSE,
ResizeNESW
};
// ============================================================================
// Window 类 - SDL2 Window + GLES 3.2 封装
// ============================================================================
class Window {
public:
Window();
~Window();
// 创建窗口
bool create(const WindowConfig& config);
void destroy();
// 窗口操作
void pollEvents();
void swapBuffers();
bool shouldClose() const;
void setShouldClose(bool close);
// 窗口属性 (Switch 上大部分为空操作)
void setTitle(const String& title);
void setSize(int width, int height);
void setPosition(int x, int y);
void setFullscreen(bool fullscreen);
void setVSync(bool enabled);
void setResizable(bool resizable);
// 获取窗口属性
int getWidth() const { return width_; }
int getHeight() const { return height_; }
Size getSize() const { return Size(static_cast<float>(width_), static_cast<float>(height_)); }
Vec2 getPosition() const { return Vec2::Zero(); }
bool isFullscreen() const { return true; }
bool isVSync() const { return vsync_; }
// DPI 缩放 (Switch 固定 1.0)
float getContentScaleX() const { return 1.0f; }
float getContentScaleY() const { return 1.0f; }
Vec2 getContentScale() const { return Vec2(1.0f, 1.0f); }
// 窗口状态
bool isFocused() const { return true; }
bool isMinimized() const { return false; }
bool isMaximized() const { return true; }
// 获取 SDL2 窗口和 GL 上下文
SDL_Window* getSDLWindow() const { return sdlWindow_; }
SDL_GLContext getGLContext() const { return glContext_; }
// 设置/获取用户数据
void setUserData(void* data) { userData_ = data; }
void* getUserData() const { return userData_; }
// 事件队列
void setEventQueue(EventQueue* queue) { eventQueue_ = queue; }
EventQueue* getEventQueue() const { return eventQueue_; }
// 获取输入管理器
Input* getInput() const { return input_.get(); }
// 光标操作 (Switch 上为空操作)
void setCursor(CursorShape shape);
void resetCursor();
// 窗口回调
using ResizeCallback = std::function<void(int width, int height)>;
using FocusCallback = std::function<void(bool focused)>;
using CloseCallback = std::function<void()>;
void setResizeCallback(ResizeCallback callback) { resizeCallback_ = callback; }
void setFocusCallback(FocusCallback callback) { focusCallback_ = callback; }
void setCloseCallback(CloseCallback callback) { closeCallback_ = callback; }
private:
// SDL2 状态
SDL_Window* sdlWindow_;
SDL_GLContext glContext_;
int width_;
int height_;
bool vsync_;
bool shouldClose_;
void* userData_;
EventQueue* eventQueue_;
UniquePtr<Input> input_;
ResizeCallback resizeCallback_;
FocusCallback focusCallback_;
CloseCallback closeCallback_;
bool initSDL();
void deinitSDL();
};
} // namespace easy2d

View File

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

View File

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

View File

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

View File

@ -0,0 +1,147 @@
#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

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