commit 87cd46a403011c201ff74c9c7e355d8733752ccb Author: ChestnutYueyue <952134128@qq.com> Date: Mon Feb 9 12:13:02 2026 +0800 feat: 添加推箱子游戏示例及相关资源文件 添加推箱子游戏示例代码,包括游戏场景、音频控制器、UI按钮等核心组件。新增游戏资源文件如图片、音效、字体等。同时完善动画系统、脚本绑定、渲染后端等基础功能模块,并引入Squirrel脚本支持。 新增功能包括: - 推箱子游戏核心逻辑与场景管理 - 音频播放与控制功能 - 游戏数据存储与读取 - 基础UI组件实现 - Squirrel脚本绑定系统 - OpenGL渲染后端支持 - 动画系统基础框架 资源文件包括: - 游戏角色、箱子、墙壁等素材图片 - 背景音乐和音效文件 - 游戏字体文件 - 游戏配置文件 diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..f5f124b --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "Bash(xargs:*)", + "Bash(wc -l \"C:\\\\Users\\\\soulcoco\\\\Desktop\\\\Easy2D\\\\Easy2D-dev\\\\Easy2D\\\\src\"/**/*.cpp)", + "Bash(wc -l:*)" + ] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a871c03 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/Easy2D/examples/push_box/src/core/audio_context.cpp b/Easy2D/examples/push_box/src/core/audio_context.cpp new file mode 100644 index 0000000..583fe6c --- /dev/null +++ b/Easy2D/examples/push_box/src/core/audio_context.cpp @@ -0,0 +1,14 @@ +#include "audio_context.h" + +namespace pushbox { + +static easy2d::WeakPtr g_audioController; + +void setAudioController(const easy2d::Ptr& controller) { + g_audioController = controller; +} + +easy2d::Ptr getAudioController() { return g_audioController.lock(); } + +} // namespace pushbox + diff --git a/Easy2D/examples/push_box/src/core/audio_context.h b/Easy2D/examples/push_box/src/core/audio_context.h new file mode 100644 index 0000000..f842cfc --- /dev/null +++ b/Easy2D/examples/push_box/src/core/audio_context.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace pushbox { + +class AudioController; + +void setAudioController(const easy2d::Ptr& controller); +easy2d::Ptr getAudioController(); + +} // namespace pushbox + diff --git a/Easy2D/examples/push_box/src/core/data.cpp b/Easy2D/examples/push_box/src/core/data.cpp new file mode 100644 index 0000000..073e19e --- /dev/null +++ b/Easy2D/examples/push_box/src/core/data.cpp @@ -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 + diff --git a/Easy2D/examples/push_box/src/core/data.h b/Easy2D/examples/push_box/src/core/data.h new file mode 100644 index 0000000..60d0c5a --- /dev/null +++ b/Easy2D/examples/push_box/src/core/data.h @@ -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 + diff --git a/Easy2D/examples/push_box/src/core/storage.cpp b/Easy2D/examples/push_box/src/core/storage.cpp new file mode 100644 index 0000000..b71c550 --- /dev/null +++ b/Easy2D/examples/push_box/src/core/storage.cpp @@ -0,0 +1,148 @@ +#include "storage.h" + +#include +#include +#include +#include + +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 diff --git a/Easy2D/examples/push_box/src/core/storage.h b/Easy2D/examples/push_box/src/core/storage.h new file mode 100644 index 0000000..e94dab8 --- /dev/null +++ b/Easy2D/examples/push_box/src/core/storage.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +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 + diff --git a/Easy2D/examples/push_box/src/main.cpp b/Easy2D/examples/push_box/src/main.cpp new file mode 100644 index 0000000..259b609 --- /dev/null +++ b/Easy2D/examples/push_box/src/main.cpp @@ -0,0 +1,103 @@ +#include +#include + +// Pushbox 游戏核心头文件 +#include "core/data.h" +#include "core/storage.h" +#include "scenes/start_scene.h" + +// Nintendo Switch 平台支持 +#ifdef __SWITCH__ +#include +#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()); + + 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; +} diff --git a/Easy2D/examples/push_box/src/nodes/audio_controller.cpp b/Easy2D/examples/push_box/src/nodes/audio_controller.cpp new file mode 100644 index 0000000..430a03f --- /dev/null +++ b/Easy2D/examples/push_box/src/nodes/audio_controller.cpp @@ -0,0 +1,61 @@ +#include "audio_controller.h" + +#include "../core/storage.h" + +namespace pushbox { + +easy2d::Ptr AudioController::create() { return easy2d::makePtr(); } + +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 + diff --git a/Easy2D/examples/push_box/src/nodes/audio_controller.h b/Easy2D/examples/push_box/src/nodes/audio_controller.h new file mode 100644 index 0000000..9e34383 --- /dev/null +++ b/Easy2D/examples/push_box/src/nodes/audio_controller.h @@ -0,0 +1,30 @@ +#pragma once + +#include "../core/data.h" +#include + +namespace pushbox { + +class AudioController : public easy2d::Node { +public: + static easy2d::Ptr 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 background_; + easy2d::Ptr manMove_; + easy2d::Ptr boxMove_; +}; + +} // namespace pushbox + diff --git a/Easy2D/examples/push_box/src/romfs/assets/audio/background.wav b/Easy2D/examples/push_box/src/romfs/assets/audio/background.wav new file mode 100644 index 0000000..8b669c6 Binary files /dev/null and b/Easy2D/examples/push_box/src/romfs/assets/audio/background.wav differ diff --git a/Easy2D/examples/push_box/src/romfs/assets/audio/boxmove.wav b/Easy2D/examples/push_box/src/romfs/assets/audio/boxmove.wav new file mode 100644 index 0000000..cda7cc0 Binary files /dev/null and b/Easy2D/examples/push_box/src/romfs/assets/audio/boxmove.wav differ diff --git a/Easy2D/examples/push_box/src/romfs/assets/audio/manmove.wav b/Easy2D/examples/push_box/src/romfs/assets/audio/manmove.wav new file mode 100644 index 0000000..c13f1bd Binary files /dev/null and b/Easy2D/examples/push_box/src/romfs/assets/audio/manmove.wav differ diff --git a/Easy2D/examples/push_box/src/romfs/assets/font.ttf b/Easy2D/examples/push_box/src/romfs/assets/font.ttf new file mode 100644 index 0000000..8682d94 Binary files /dev/null and b/Easy2D/examples/push_box/src/romfs/assets/font.ttf differ diff --git a/Easy2D/examples/push_box/src/romfs/assets/images/box.gif b/Easy2D/examples/push_box/src/romfs/assets/images/box.gif new file mode 100644 index 0000000..4c835fd Binary files /dev/null and b/Easy2D/examples/push_box/src/romfs/assets/images/box.gif differ diff --git a/Easy2D/examples/push_box/src/romfs/assets/images/boxinpoint.gif b/Easy2D/examples/push_box/src/romfs/assets/images/boxinpoint.gif new file mode 100644 index 0000000..a12c967 Binary files /dev/null and b/Easy2D/examples/push_box/src/romfs/assets/images/boxinpoint.gif differ diff --git a/Easy2D/examples/push_box/src/romfs/assets/images/floor.gif b/Easy2D/examples/push_box/src/romfs/assets/images/floor.gif new file mode 100644 index 0000000..321d058 Binary files /dev/null and b/Easy2D/examples/push_box/src/romfs/assets/images/floor.gif differ diff --git a/Easy2D/examples/push_box/src/romfs/assets/images/player/mandown.gif b/Easy2D/examples/push_box/src/romfs/assets/images/player/mandown.gif new file mode 100644 index 0000000..98ccb72 Binary files /dev/null and b/Easy2D/examples/push_box/src/romfs/assets/images/player/mandown.gif differ diff --git a/Easy2D/examples/push_box/src/romfs/assets/images/player/manhanddown.gif b/Easy2D/examples/push_box/src/romfs/assets/images/player/manhanddown.gif new file mode 100644 index 0000000..6e1ba0f Binary files /dev/null and b/Easy2D/examples/push_box/src/romfs/assets/images/player/manhanddown.gif differ diff --git a/Easy2D/examples/push_box/src/romfs/assets/images/player/manhandleft.gif b/Easy2D/examples/push_box/src/romfs/assets/images/player/manhandleft.gif new file mode 100644 index 0000000..18326bf Binary files /dev/null and b/Easy2D/examples/push_box/src/romfs/assets/images/player/manhandleft.gif differ diff --git a/Easy2D/examples/push_box/src/romfs/assets/images/player/manhandright.gif b/Easy2D/examples/push_box/src/romfs/assets/images/player/manhandright.gif new file mode 100644 index 0000000..e6e3618 Binary files /dev/null and b/Easy2D/examples/push_box/src/romfs/assets/images/player/manhandright.gif differ diff --git a/Easy2D/examples/push_box/src/romfs/assets/images/player/manhandup.gif b/Easy2D/examples/push_box/src/romfs/assets/images/player/manhandup.gif new file mode 100644 index 0000000..33e4a79 Binary files /dev/null and b/Easy2D/examples/push_box/src/romfs/assets/images/player/manhandup.gif differ diff --git a/Easy2D/examples/push_box/src/romfs/assets/images/player/manleft.gif b/Easy2D/examples/push_box/src/romfs/assets/images/player/manleft.gif new file mode 100644 index 0000000..e875c28 Binary files /dev/null and b/Easy2D/examples/push_box/src/romfs/assets/images/player/manleft.gif differ diff --git a/Easy2D/examples/push_box/src/romfs/assets/images/player/manright.gif b/Easy2D/examples/push_box/src/romfs/assets/images/player/manright.gif new file mode 100644 index 0000000..bf94f12 Binary files /dev/null and b/Easy2D/examples/push_box/src/romfs/assets/images/player/manright.gif differ diff --git a/Easy2D/examples/push_box/src/romfs/assets/images/player/manup.gif b/Easy2D/examples/push_box/src/romfs/assets/images/player/manup.gif new file mode 100644 index 0000000..7211b42 Binary files /dev/null and b/Easy2D/examples/push_box/src/romfs/assets/images/player/manup.gif differ diff --git a/Easy2D/examples/push_box/src/romfs/assets/images/point.gif b/Easy2D/examples/push_box/src/romfs/assets/images/point.gif new file mode 100644 index 0000000..b81cbe4 Binary files /dev/null and b/Easy2D/examples/push_box/src/romfs/assets/images/point.gif differ diff --git a/Easy2D/examples/push_box/src/romfs/assets/images/soundoff.png b/Easy2D/examples/push_box/src/romfs/assets/images/soundoff.png new file mode 100644 index 0000000..246abd1 Binary files /dev/null and b/Easy2D/examples/push_box/src/romfs/assets/images/soundoff.png differ diff --git a/Easy2D/examples/push_box/src/romfs/assets/images/soundon.png b/Easy2D/examples/push_box/src/romfs/assets/images/soundon.png new file mode 100644 index 0000000..592edb4 Binary files /dev/null and b/Easy2D/examples/push_box/src/romfs/assets/images/soundon.png differ diff --git a/Easy2D/examples/push_box/src/romfs/assets/images/start.jpg b/Easy2D/examples/push_box/src/romfs/assets/images/start.jpg new file mode 100644 index 0000000..0a2e7e8 Binary files /dev/null and b/Easy2D/examples/push_box/src/romfs/assets/images/start.jpg differ diff --git a/Easy2D/examples/push_box/src/romfs/assets/images/success.jpg b/Easy2D/examples/push_box/src/romfs/assets/images/success.jpg new file mode 100644 index 0000000..f12b5ac Binary files /dev/null and b/Easy2D/examples/push_box/src/romfs/assets/images/success.jpg differ diff --git a/Easy2D/examples/push_box/src/romfs/assets/images/wall.gif b/Easy2D/examples/push_box/src/romfs/assets/images/wall.gif new file mode 100644 index 0000000..448dc5e Binary files /dev/null and b/Easy2D/examples/push_box/src/romfs/assets/images/wall.gif differ diff --git a/Easy2D/examples/push_box/src/romfs/pushbox.ini b/Easy2D/examples/push_box/src/romfs/pushbox.ini new file mode 100644 index 0000000..3f7b9d0 --- /dev/null +++ b/Easy2D/examples/push_box/src/romfs/pushbox.ini @@ -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 diff --git a/Easy2D/examples/push_box/src/scenes/play_scene.cpp b/Easy2D/examples/push_box/src/scenes/play_scene.cpp new file mode 100644 index 0000000..5f71486 --- /dev/null +++ b/Easy2D/examples/push_box/src/scenes/play_scene.cpp @@ -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 + +namespace pushbox { + +static easy2d::Ptr 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(config.width), static_cast(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(soundOn->getWidth()), + static_cast(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(); + 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(), 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((12 - map_.width) / 2) * tileW; + float offsetY = static_cast((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 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(i * tileW), + offsetY + static_cast(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(), + easy2d::TransitionType::Fade, 0.25f); + return; + } + + setLevel(g_CurrentLevel + 1); +} + +} // namespace pushbox diff --git a/Easy2D/examples/push_box/src/scenes/play_scene.h b/Easy2D/examples/push_box/src/scenes/play_scene.h new file mode 100644 index 0000000..bcea8b2 --- /dev/null +++ b/Easy2D/examples/push_box/src/scenes/play_scene.h @@ -0,0 +1,45 @@ +#pragma once + +#include "../core/data.h" +#include + +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 font28_; + easy2d::Ptr font20_; + + easy2d::Ptr levelText_; + easy2d::Ptr stepText_; + easy2d::Ptr bestText_; + easy2d::Ptr mapLayer_; + + easy2d::Ptr soundBtn_; + + easy2d::Ptr texWall_; + easy2d::Ptr texPoint_; + easy2d::Ptr texFloor_; + easy2d::Ptr texBox_; + easy2d::Ptr texBoxInPoint_; + + easy2d::Ptr texMan_[5]; + easy2d::Ptr texManPush_[5]; +}; + +} // namespace pushbox diff --git a/Easy2D/examples/push_box/src/scenes/start_scene.cpp b/Easy2D/examples/push_box/src/scenes/start_scene.cpp new file mode 100644 index 0000000..3dc5073 --- /dev/null +++ b/Easy2D/examples/push_box/src/scenes/start_scene.cpp @@ -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 + +namespace pushbox { + +StartScene::StartScene() { + // 设置视口大小为窗口尺寸 + auto& app = easy2d::Application::instance(); + auto& config = app.getConfig(); + setViewportSize(static_cast(config.width), static_cast(config.height)); +} + +static easy2d::Ptr 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(app.getConfig().width) / static_cast(bgTex->getWidth()); + float sy = + static_cast(app.getConfig().height) / static_cast(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(soundOn->getWidth()), + static_cast(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(1), easy2d::TransitionType::Fade, 0.25f); +} + +void StartScene::continueGame() { + easy2d::Application::instance().scenes().replaceScene( + easy2d::makePtr(g_CurrentLevel), easy2d::TransitionType::Fade, 0.25f); +} + +void StartScene::exitGame() { easy2d::Application::instance().quit(); } + +} // namespace pushbox diff --git a/Easy2D/examples/push_box/src/scenes/start_scene.h b/Easy2D/examples/push_box/src/scenes/start_scene.h new file mode 100644 index 0000000..d2515f9 --- /dev/null +++ b/Easy2D/examples/push_box/src/scenes/start_scene.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +namespace pushbox { + +class MenuButton; + +class StartScene : public easy2d::Scene { +public: + StartScene(); + void onEnter() override; + +private: + void startNewGame(); + void continueGame(); + void exitGame(); + + easy2d::Ptr resumeBtn_; + easy2d::Ptr soundBtn_; + easy2d::Ptr font_; +}; + +} // namespace pushbox diff --git a/Easy2D/examples/push_box/src/scenes/success_scene.cpp b/Easy2D/examples/push_box/src/scenes/success_scene.cpp new file mode 100644 index 0000000..5250e66 --- /dev/null +++ b/Easy2D/examples/push_box/src/scenes/success_scene.cpp @@ -0,0 +1,56 @@ +#include "success_scene.h" + +#include "../ui/menu_button.h" +#include +#include +#include +#include + +namespace pushbox { + +SuccessScene::SuccessScene() { + // 设置视口大小为窗口尺寸 + auto& app = easy2d::Application::instance(); + auto& config = app.getConfig(); + setViewportSize(static_cast(config.width), static_cast(config.height)); +} + +static easy2d::Ptr 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(app.getConfig().width) / static_cast(bgTex->getWidth()); + float sy = + static_cast(app.getConfig().height) / static_cast(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 diff --git a/Easy2D/examples/push_box/src/scenes/success_scene.h b/Easy2D/examples/push_box/src/scenes/success_scene.h new file mode 100644 index 0000000..bbc6c27 --- /dev/null +++ b/Easy2D/examples/push_box/src/scenes/success_scene.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace pushbox { + +class SuccessScene : public easy2d::Scene { +public: + SuccessScene(); + void onEnter() override; +}; + +} // namespace pushbox diff --git a/Easy2D/examples/push_box/src/ui/menu_button.cpp b/Easy2D/examples/push_box/src/ui/menu_button.cpp new file mode 100644 index 0000000..b68742d --- /dev/null +++ b/Easy2D/examples/push_box/src/ui/menu_button.cpp @@ -0,0 +1,58 @@ +#include "menu_button.h" + +#include +#include + +namespace pushbox { + +easy2d::Ptr MenuButton::create(easy2d::Ptr font, + const easy2d::String& text, + easy2d::Function onClick) { + auto btn = easy2d::makePtr(); + 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(btn)]() { + if (auto self = wbtn.lock()) { + if (self->enabled_ && self->onClick_) { + self->onClick_(); + } + } + }); + + btn->getEventDispatcher().addListener( + easy2d::EventType::UIHoverEnter, + [wbtn = easy2d::WeakPtr(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(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 + diff --git a/Easy2D/examples/push_box/src/ui/menu_button.h b/Easy2D/examples/push_box/src/ui/menu_button.h new file mode 100644 index 0000000..da2b8cd --- /dev/null +++ b/Easy2D/examples/push_box/src/ui/menu_button.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +namespace pushbox { + +class MenuButton : public easy2d::Button { +public: + static easy2d::Ptr create(easy2d::Ptr font, + const easy2d::String& text, + easy2d::Function onClick); + + void setEnabled(bool enabled); + bool isEnabled() const { return enabled_; } + +private: + bool enabled_ = true; + easy2d::Function onClick_; +}; + +} // namespace pushbox diff --git a/Easy2D/examples/switch_simple_test/main.cpp b/Easy2D/examples/switch_simple_test/main.cpp new file mode 100644 index 0000000..d3d0c68 --- /dev/null +++ b/Easy2D/examples/switch_simple_test/main.cpp @@ -0,0 +1,151 @@ +#include +#include + +// Nintendo Switch 平台支持 +#ifdef __SWITCH__ +#include +#endif + +using namespace easy2d; + +// 加载系统字体的辅助函数 +easy2d::Ptr loadSystemFont(int size) { + auto &resources = Application::instance().resources(); + easy2d::Ptr 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(); + Application::instance().scenes().pushScene(scene); + + std::cout << "Scene started!" << std::endl; + + // 运行主循环 + Application::instance().run(); + + // 清理 + Application::instance().shutdown(); + +#ifdef __SWITCH__ + romfsExit(); + socketExit(); +#endif + + return 0; +} diff --git a/Easy2D/examples/switch_simple_test/romfs/font.ttf b/Easy2D/examples/switch_simple_test/romfs/font.ttf new file mode 100644 index 0000000..8682d94 Binary files /dev/null and b/Easy2D/examples/switch_simple_test/romfs/font.ttf differ diff --git a/Easy2D/include/easy2d/action/action.h b/Easy2D/include/easy2d/action/action.h new file mode 100644 index 0000000..b81a69f --- /dev/null +++ b/Easy2D/include/easy2d/action/action.h @@ -0,0 +1,79 @@ +#pragma once + +#include +#include + +namespace easy2d { + class Node; + + enum class ActionState + { + Idle, + Running, + Paused, + Completed + }; + + class Action + { + public: + using ProgressCallback = std::function; + using CompletionCallback = std::function; + + 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; +} diff --git a/Easy2D/include/easy2d/action/actions.h b/Easy2D/include/easy2d/action/actions.h new file mode 100644 index 0000000..d2e46be --- /dev/null +++ b/Easy2D/include/easy2d/action/actions.h @@ -0,0 +1,286 @@ +#pragma once + +#include "easy2d/action/action.h" +#include "easy2d/core/math_types.h" +#include +#include + +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& actions); + ~Sequence(); + + Action* clone() const override; + Action* reverse() const override; + + protected: + void onStart() override; + void onUpdate(float progress) override; + + private: + std::vector actions_; + int currentIndex_ = 0; + float split_ = 0.0f; + float last_ = 0.0f; + }; + + class Spawn : public IntervalAction + { + public: + Spawn(const std::vector& actions); + ~Spawn(); + + Action* clone() const override; + Action* reverse() const override; + + protected: + void onStart() override; + void onUpdate(float progress) override; + + private: + std::vector 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; + + 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& actions) { return new Sequence(actions); } + inline Spawn* spawn(const std::vector& 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)); } +} diff --git a/Easy2D/include/easy2d/action/ease.h b/Easy2D/include/easy2d/action/ease.h new file mode 100644 index 0000000..b56cb8f --- /dev/null +++ b/Easy2D/include/easy2d/action/ease.h @@ -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); + }; +} diff --git a/Easy2D/include/easy2d/animation/als_parser.h b/Easy2D/include/easy2d/animation/als_parser.h new file mode 100644 index 0000000..c0e3ed0 --- /dev/null +++ b/Easy2D/include/easy2d/animation/als_parser.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include + +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 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 diff --git a/Easy2D/include/easy2d/animation/ani_binary_parser.h b/Easy2D/include/easy2d/animation/ani_binary_parser.h new file mode 100644 index 0000000..e6599f7 --- /dev/null +++ b/Easy2D/include/easy2d/animation/ani_binary_parser.h @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +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 diff --git a/Easy2D/include/easy2d/animation/ani_parser.h b/Easy2D/include/easy2d/animation/ani_parser.h new file mode 100644 index 0000000..d7c2b27 --- /dev/null +++ b/Easy2D/include/easy2d/animation/ani_parser.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace easy2d { + +// ============================================================================ +// ANI 文件解析结果 +// ============================================================================ +struct AniParseResult { + bool success = false; + std::string errorMessage; + Ptr clip; +}; + +// ============================================================================ +// AniParser - ANI 脚本文件解析器 +// 将原始 ANI 文件格式解析为 AnimationClip 数据 +// ============================================================================ +class AniParser { +public: + AniParser() = default; + + /// 从文件解析 + AniParseResult parse(const std::string& filePath); + + /// 从内存内容解析 + AniParseResult parseFromMemory(const std::string& content, + const std::string& basePath = ""); + + /// 设置路径替换回调(对应原始 AdditionalOptions) + void setPathResolver(PathResolveCallback callback) { + pathResolver_ = std::move(callback); + } + + /// 设置基础路径(用于解析相对路径) + void setBasePath(const std::string& basePath) { + basePath_ = basePath; + } + +private: + PathResolveCallback pathResolver_; + std::string basePath_; + + std::string resolvePath(const std::string& relativePath) const; +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/animation/animated_sprite.h b/Easy2D/include/easy2d/animation/animated_sprite.h new file mode 100644 index 0000000..2cbea96 --- /dev/null +++ b/Easy2D/include/easy2d/animation/animated_sprite.h @@ -0,0 +1,127 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace easy2d { + +// ============================================================================ +// AnimatedSprite - 动画精灵节点 +// 将 AnimationController 与 Sprite 渲染桥接,接入场景图 +// ============================================================================ +class AnimatedSprite : public Sprite { +public: + AnimatedSprite(); + ~AnimatedSprite() override = default; + + // ------ 静态工厂 ------ + static Ptr create(); + static Ptr create(Ptr clip); + static Ptr create(const std::string& aniFilePath); + + // ------ 动画绑定 ------ + void setAnimationClip(Ptr clip); + void loadAnimation(const std::string& aniFilePath); + Ptr getAnimationClip() const; + + // ------ 动画字典 ------ + void addAnimation(const std::string& name, Ptr clip); + void play(const std::string& name, bool loop = true); + bool hasAnimation(const std::string& name) const; + Ptr getAnimation(const std::string& name) const; + const std::string& getCurrentAnimationName() const; + + // ------ 播放控制(委托 controller_)------ + void play(); + void pause(); + void resume(); + void stop(); + void reset(); + bool isPlaying() const; + bool isPaused() const; + bool isStopped() const; + + // ------ 属性控制 ------ + void setLooping(bool loop); + bool isLooping() const; + void setPlaybackSpeed(float speed); + float getPlaybackSpeed() const; + + // ------ 帧控制 ------ + void setFrameIndex(size_t index); + size_t getCurrentFrameIndex() const; + size_t getTotalFrames() const; + void nextFrame(); + void prevFrame(); + + // ------ 帧范围限制 ------ + /// 设置帧播放范围(用于精灵图动画,限制在指定范围内循环) + /// @param start 起始帧索引(包含) + /// @param end 结束帧索引(包含),-1表示不限制 + void setFrameRange(int start, int end = -1); + + /// 获取当前帧范围 + /// @return pair<起始帧, 结束帧>,结束帧为-1表示不限制 + std::pair getFrameRange() const; + + /// 清除帧范围限制(恢复播放所有帧) + void clearFrameRange(); + + /// 检查是否设置了帧范围限制 + bool hasFrameRange() const; + + // ------ 回调 ------ + void setCompletionCallback(AnimationController::CompletionCallback cb); + void setKeyframeCallback(AnimationController::KeyframeCallback cb); + void setSoundTriggerCallback(AnimationController::SoundTriggerCallback cb); + + // ------ 碰撞盒访问(当前帧)------ + const std::vector>& getCurrentDamageBoxes() const; + const std::vector>& getCurrentAttackBoxes() const; + + // ------ 帧变换控制 ------ + /// 设置是否由动画帧数据覆盖节点的 position/scale/rotation + /// ANI 动画需要开启(默认),精灵图动画应关闭 + void setApplyFrameTransform(bool apply) { applyFrameTransform_ = apply; } + bool isApplyFrameTransform() const { return applyFrameTransform_; } + + // ------ 自动播放 ------ + void setAutoPlay(bool autoPlay) { autoPlay_ = autoPlay; } + bool isAutoPlay() const { return autoPlay_; } + + // ------ 直接控制器访问 ------ + AnimationController& getController() { return controller_; } + const AnimationController& getController() const { return controller_; } + +protected: + void onUpdate(float dt) override; + void onEnter() override; + +private: + AnimationController controller_; + bool autoPlay_ = false; + bool applyFrameTransform_ = true; + + // 动画字典 + std::unordered_map> animations_; + std::string currentAnimationName_; + static const std::string emptyString_; + + // 帧范围限制(用于精灵图动画) + int frameRangeStart_ = 0; // 起始帧索引 + int frameRangeEnd_ = -1; // 结束帧索引,-1表示不限制 + + // 空碰撞盒列表(用于无帧时返回引用) + static const std::vector> emptyBoxes_; + + void applyFrame(const AnimationFrame& frame); + void onFrameChanged(size_t oldIdx, size_t newIdx, const AnimationFrame& frame); +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/animation/animation_cache.h b/Easy2D/include/easy2d/animation/animation_cache.h new file mode 100644 index 0000000..ce8609b --- /dev/null +++ b/Easy2D/include/easy2d/animation/animation_cache.h @@ -0,0 +1,103 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace easy2d { + +// 路径替换回调(对应原始 AdditionalOptions) +using PathResolveCallback = std::function; + +// ============================================================================ +// AnimationCache - 动画片段全局缓存(借鉴 Cocos AnimationCache) +// 同一 ANI 文件只解析一次,后续直接复用数据 +// ============================================================================ +class AnimationCache { +public: + static AnimationCache& getInstance() { + static AnimationCache instance; + return instance; + } + + // ------ 加载与获取 ------ + + /// 从文件加载(自动缓存),已缓存则直接返回 + /// 注意:实际的 ANI 解析逻辑在 AniParser 中实现 + /// 此方法在 animation_cache.cpp 中实现,依赖 AniParser + Ptr loadClip(const std::string& aniFilePath); + + /// 从缓存获取(不触发加载) + Ptr getClip(const std::string& name) const { + std::lock_guard lock(mutex_); + auto it = clips_.find(name); + if (it != clips_.end()) return it->second; + return nullptr; + } + + /// 手动添加到缓存 + void addClip(Ptr clip, const std::string& name) { + std::lock_guard lock(mutex_); + clips_[name] = std::move(clip); + } + + // ------ 缓存管理 ------ + + bool has(const std::string& name) const { + std::lock_guard lock(mutex_); + return clips_.find(name) != clips_.end(); + } + + void removeClip(const std::string& name) { + std::lock_guard lock(mutex_); + clips_.erase(name); + } + + /// 移除未被外部引用的动画片段 + void removeUnusedClips() { + std::lock_guard lock(mutex_); + for (auto it = clips_.begin(); it != clips_.end(); ) { + if (it->second.use_count() == 1) { + it = clips_.erase(it); + } else { + ++it; + } + } + } + + void clear() { + std::lock_guard lock(mutex_); + clips_.clear(); + } + + size_t count() const { + std::lock_guard lock(mutex_); + return clips_.size(); + } + + // ------ 路径配置 ------ + void setPathResolver(PathResolveCallback resolver) { + pathResolver_ = std::move(resolver); + } + + PathResolveCallback getPathResolver() const { + return pathResolver_; + } + +private: + AnimationCache() = default; + ~AnimationCache() = default; + AnimationCache(const AnimationCache&) = delete; + AnimationCache& operator=(const AnimationCache&) = delete; + + mutable std::mutex mutex_; + std::unordered_map> clips_; + PathResolveCallback pathResolver_; +}; + +// 便捷宏 +#define E2D_ANIMATION_CACHE() ::easy2d::AnimationCache::getInstance() + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/animation/animation_clip.h b/Easy2D/include/easy2d/animation/animation_clip.h new file mode 100644 index 0000000..631c967 --- /dev/null +++ b/Easy2D/include/easy2d/animation/animation_clip.h @@ -0,0 +1,195 @@ +#pragma once + +#include +#include +#include +#include +#include + +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(index), frame); + } + + void removeFrame(size_t index) { + assert(index < frames_.size()); + frames_.erase(frames_.begin() + static_cast(index)); + } + + void clearFrames() { + frames_.clear(); + } + + const AnimationFrame& getFrame(size_t index) const { + assert(index < frames_.size()); + return frames_[index]; + } + + AnimationFrame& getFrame(size_t index) { + assert(index < frames_.size()); + return frames_[index]; + } + + size_t getFrameCount() const { return frames_.size(); } + bool empty() const { return frames_.empty(); } + + // ------ 全局属性(对应原始 AnimationFlag)------ + FramePropertySet& globalProperties() { return globalProperties_; } + const FramePropertySet& globalProperties() const { return globalProperties_; } + + bool isLooping() const { + return globalProperties_.getOr(FramePropertyKey::Loop, false); + } + + void setLooping(bool loop) { + globalProperties_.withLoop(loop); + } + + // ------ 时间信息 ------ + float getTotalDuration() const { + float total = 0.0f; + for (const auto& frame : frames_) { + total += frame.delay; + } + return total; + } + + // ------ 预计算最大帧尺寸 ------ + Size getMaxFrameSize() const { + Size maxSize; + for (const auto& frame : frames_) { + if (frame.spriteFrame && frame.spriteFrame->isValid()) { + const auto& rect = frame.spriteFrame->getRect(); + if (rect.size.width > maxSize.width) + maxSize.width = rect.size.width; + if (rect.size.height > maxSize.height) + maxSize.height = rect.size.height; + } + } + return maxSize; + } + + // ------ 元数据 ------ + void setName(const std::string& name) { name_ = name; } + const std::string& getName() const { return name_; } + + void setSourcePath(const std::string& path) { sourcePath_ = path; } + const std::string& getSourcePath() const { return sourcePath_; } + + // ------ 静态工厂 ------ + static Ptr create(const std::string& name = "") { + return makePtr(name); + } + + /// 从精灵图网格创建(所有帧按顺序) + static Ptr createFromGrid( + Ptr texture, int frameWidth, int frameHeight, + float frameDurationMs = 100.0f, int frameCount = -1, + int spacing = 0, int margin = 0) + { + if (!texture) return nullptr; + + int texW = texture->getWidth(); + int texH = texture->getHeight(); + int usableW = texW - 2 * margin; + int usableH = texH - 2 * margin; + int cols = (usableW + spacing) / (frameWidth + spacing); + int rows = (usableH + spacing) / (frameHeight + spacing); + int total = (frameCount > 0) ? frameCount : cols * rows; + + auto clip = makePtr(); + for (int i = 0; i < total; ++i) { + int col = i % cols; + int row = i / cols; + if (row >= rows) break; + + // 翻转行顺序:精灵图第0行在顶部,但OpenGL纹理V坐标从底部开始 + // 所以将行索引翻转,使第0行对应纹理底部(V=1.0),第3行对应纹理顶部(V=0.0) + int flippedRow = (rows - 1) - row; + + Rect rect( + static_cast(margin + col * (frameWidth + spacing)), + static_cast(margin + flippedRow * (frameHeight + spacing)), + static_cast(frameWidth), + static_cast(frameHeight) + ); + + auto sf = SpriteFrame::create(texture, rect); + AnimationFrame frame; + frame.spriteFrame = std::move(sf); + frame.delay = frameDurationMs; + clip->addFrame(std::move(frame)); + } + return clip; + } + + /// 从精灵图网格创建(指定帧索引列表) + static Ptr createFromGridIndices( + Ptr texture, int frameWidth, int frameHeight, + const std::vector& frameIndices, + float frameDurationMs = 100.0f, + int spacing = 0, int margin = 0) + { + if (!texture) return nullptr; + + int texW = texture->getWidth(); + int texH = texture->getHeight(); + int usableW = texW - 2 * margin; + int usableH = texH - 2 * margin; + int cols = (usableW + spacing) / (frameWidth + spacing); + int rows = (usableH + spacing) / (frameHeight + spacing); + + auto clip = makePtr(); + for (int idx : frameIndices) { + int col = idx % cols; + int row = idx / cols; + + // 翻转行顺序:精灵图第0行在顶部,但OpenGL纹理V坐标从底部开始 + int flippedRow = (rows - 1) - row; + + Rect rect( + static_cast(margin + col * (frameWidth + spacing)), + static_cast(margin + flippedRow * (frameHeight + spacing)), + static_cast(frameWidth), + static_cast(frameHeight) + ); + + auto sf = SpriteFrame::create(texture, rect); + AnimationFrame frame; + frame.spriteFrame = std::move(sf); + frame.delay = frameDurationMs; + clip->addFrame(std::move(frame)); + } + return clip; + } + +private: + std::string name_; + std::string sourcePath_; + std::vector frames_; + FramePropertySet globalProperties_; +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/animation/animation_controller.h b/Easy2D/include/easy2d/animation/animation_controller.h new file mode 100644 index 0000000..d23a026 --- /dev/null +++ b/Easy2D/include/easy2d/animation/animation_controller.h @@ -0,0 +1,106 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace easy2d { + +// ============================================================================ +// 动画播放状态 +// ============================================================================ +enum class AnimPlayState : uint8 { + Stopped, + Playing, + Paused +}; + +// ============================================================================ +// AnimationController - 动画播放控制器 +// 借鉴 Cocos Creator 的 AnimationState:纯播放逻辑,不持有渲染资源 +// ============================================================================ +class AnimationController { +public: + // 回调类型定义 + using FrameChangeCallback = std::function; + using KeyframeCallback = std::function; + using SoundTriggerCallback = std::function; + using CompletionCallback = std::function; + + AnimationController() = default; + + // ------ 绑定动画数据 ------ + void setClip(Ptr clip); + Ptr getClip() const { return clip_; } + + // ------ 播放控制 ------ + void play(); + void pause(); + void resume(); + void stop(); + void reset(); + + // ------ 帧控制 ------ + void setFrameIndex(size_t index); + void nextFrame(); + void prevFrame(); + + // ------ 核心更新(每帧调用)------ + void update(float dt); + + // ------ 状态查询 ------ + AnimPlayState getState() const { return state_; } + bool isPlaying() const { return state_ == AnimPlayState::Playing; } + bool isPaused() const { return state_ == AnimPlayState::Paused; } + bool isStopped() const { return state_ == AnimPlayState::Stopped; } + + size_t getCurrentFrameIndex() const { return currentFrameIndex_; } + size_t getTotalFrames() const; + const AnimationFrame& getCurrentFrame() const; + + float getPlaybackSpeed() const { return playbackSpeed_; } + void setPlaybackSpeed(float speed) { playbackSpeed_ = speed; } + + bool isLooping() const; + void setLooping(bool loop); + + // ------ 插值状态 ------ + float getInterpolationFactor() const { return interpolationFactor_; } + bool isInterpolating() const { return interpolating_; } + + // ------ 回调注册 ------ + void setFrameChangeCallback(FrameChangeCallback cb) { onFrameChange_ = std::move(cb); } + void setKeyframeCallback(KeyframeCallback cb) { onKeyframe_ = std::move(cb); } + void setSoundTriggerCallback(SoundTriggerCallback cb) { onSoundTrigger_ = std::move(cb); } + void setCompletionCallback(CompletionCallback cb) { onComplete_ = std::move(cb); } + +private: + Ptr clip_; + AnimPlayState state_ = AnimPlayState::Stopped; + + size_t currentFrameIndex_ = 0; + float accumulatedTime_ = 0.0f; // 当前帧已累积时间 (ms) + float playbackSpeed_ = 1.0f; + bool loopOverride_ = false; // 外部循环覆盖值 + bool hasLoopOverride_ = false; // 是否使用外部循环覆盖 + + // 插值状态 + bool interpolating_ = false; + float interpolationFactor_ = 0.0f; + + // 回调 + FrameChangeCallback onFrameChange_; + KeyframeCallback onKeyframe_; + SoundTriggerCallback onSoundTrigger_; + CompletionCallback onComplete_; + + // 内部方法 + void advanceFrame(size_t newIndex); + void processFrameProperties(const AnimationFrame& frame); + void updateInterpolation(); +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/animation/animation_event.h b/Easy2D/include/easy2d/animation/animation_event.h new file mode 100644 index 0000000..6eb7757 --- /dev/null +++ b/Easy2D/include/easy2d/animation/animation_event.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include + +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; +using KeyframeHitCallback = std::function; +using AnimationCompleteCallback = std::function; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/animation/animation_frame.h b/Easy2D/include/easy2d/animation/animation_frame.h new file mode 100644 index 0000000..19cba10 --- /dev/null +++ b/Easy2D/include/easy2d/animation/animation_frame.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace easy2d { + +// ============================================================================ +// AnimationFrame - 单帧数据 +// 引用 SpriteFrame 而非直接持有纹理(借鉴 Cocos 模式) +// 通过 FramePropertySet 支持不固定数据(ANI Flag 系统增强版) +// ============================================================================ +struct AnimationFrame { + // ------ 核心数据(固定部分)------ + Ptr spriteFrame; // 精灵帧引用(Cocos 模式) + std::string texturePath; // 原始图片路径(用于解析时定位资源) + int textureIndex = 0; // 精灵图集索引 + Vec2 offset; // 位置偏移 + float delay = 100.0f; // 帧延迟(毫秒) + + // ------ 碰撞盒数据(DNF ANI 格式)------ + std::vector> damageBoxes; // 伤害碰撞盒 + std::vector> attackBoxes; // 攻击碰撞盒 + + // ------ 不固定数据(属性集合)------ + FramePropertySet properties; // 类型安全的 Flag 系统 + + // ------ 便捷方法 ------ + bool hasTexture() const { + return spriteFrame != nullptr && spriteFrame->isValid(); + } + + bool hasInterpolation() const { + return properties.getOr(FramePropertyKey::Interpolation, false); + } + + bool hasKeyframeCallback() const { + return properties.has(FramePropertyKey::SetFlag); + } + + int getKeyframeIndex() const { + return properties.getOr(FramePropertyKey::SetFlag, -1); + } + + Vec2 getEffectiveScale() const { + return properties.getOr(FramePropertyKey::ImageRate, Vec2::One()); + } + + float getEffectiveRotation() const { + return properties.getOr(FramePropertyKey::ImageRotate, 0.0f); + } + + Color getEffectiveColor() const { + return properties.getOr(FramePropertyKey::ColorTint, Colors::White); + } +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/animation/animation_node.h b/Easy2D/include/easy2d/animation/animation_node.h new file mode 100644 index 0000000..8919dd5 --- /dev/null +++ b/Easy2D/include/easy2d/animation/animation_node.h @@ -0,0 +1,107 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace easy2d { + +// ============================================================================ +// AnimationNode - 动画节点(继承 Node) +// 使用 FrameRenderer 单渲染器策略,不依赖 Sprite 基类 +// 适用于需要独立渲染控制的动画(如特效、复合动画图层) +// ============================================================================ +class AnimationNode : public Node { +public: + AnimationNode(); + ~AnimationNode() override = default; + + // ------ 静态工厂(Cocos 风格)------ + static Ptr create(); + static Ptr create(Ptr clip); + static Ptr create(const std::string& aniFilePath); + + // ------ 动画数据 ------ + void setClip(Ptr clip); + Ptr getClip() const; + bool loadFromFile(const std::string& aniFilePath); + + // ------ 播放控制 ------ + void play(); + void pause(); + void resume(); + void stop(); + void reset(); + + bool isPlaying() const; + bool isPaused() const; + bool isStopped() const; + + void setPlaybackSpeed(float speed); + float getPlaybackSpeed() const; + void setLooping(bool loop); + bool isLooping() const; + + // ------ 帧控制 ------ + void setFrameIndex(size_t index); + size_t getCurrentFrameIndex() const; + size_t getTotalFrames() const; + + // ------ 事件回调 ------ + void setKeyframeCallback(KeyframeHitCallback callback); + void setCompletionCallback(AnimationCompleteCallback callback); + void setFrameChangeCallback(AnimationController::FrameChangeCallback callback); + void addEventListener(AnimationEventCallback callback); + + // ------ 视觉属性 ------ + void setTintColor(const Color& color); + Color getTintColor() const { return tintColor_; } + void setFlipX(bool flip) { flipX_ = flip; } + void setFlipY(bool flip) { flipY_ = flip; } + bool isFlipX() const { return flipX_; } + bool isFlipY() const { return flipY_; } + + // ------ 自动播放 ------ + void setAutoPlay(bool autoPlay) { autoPlay_ = autoPlay; } + bool isAutoPlay() const { return autoPlay_; } + + // ------ 碰撞盒访问 ------ + const std::vector>& getCurrentDamageBoxes() const; + const std::vector>& getCurrentAttackBoxes() const; + + // ------ 查询 ------ + Size getMaxFrameSize() const; + Rect getBoundingBox() const override; + + // ------ 直接访问 ------ + AnimationController& getController() { return controller_; } + const AnimationController& getController() const { return controller_; } + FrameRenderer& getFrameRenderer() { return frameRenderer_; } + const FrameRenderer& getFrameRenderer() const { return frameRenderer_; } + +protected: + void onUpdate(float dt) override; + void onDraw(RenderBackend& renderer) override; + void onEnter() override; + void onExit() override; + +private: + AnimationController controller_; + FrameRenderer frameRenderer_; + Color tintColor_ = Colors::White; + bool flipX_ = false; + bool flipY_ = false; + bool autoPlay_ = false; + std::vector eventListeners_; + + static const std::vector> emptyBoxes_; + + void setupControllerCallbacks(); + void dispatchEvent(const AnimationEvent& event); +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/animation/composite_animation.h b/Easy2D/include/easy2d/animation/composite_animation.h new file mode 100644 index 0000000..728bc19 --- /dev/null +++ b/Easy2D/include/easy2d/animation/composite_animation.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace easy2d { + +// ============================================================================ +// CompositeAnimation - ALS 多层复合动画节点 +// 管理多个 AnimationNode 图层,统一控制播放 +// 对应 DNF 的 ALS 格式(多层动画叠加) +// ============================================================================ +class CompositeAnimation : public Node { +public: + CompositeAnimation() = default; + ~CompositeAnimation() override = default; + + // ------ 静态工厂 ------ + static Ptr create(); + static Ptr create(const std::string& alsFilePath); + + // ------ 加载 ------ + bool loadFromFile(const std::string& alsFilePath); + + // ------ 图层管理 ------ + void addLayer(Ptr node, int zOrder = 0); + void removeLayer(size_t index); + Ptr getLayer(size_t index) const; + Ptr getMainLayer() const; + size_t getLayerCount() const; + + // ------ 统一播放控制 ------ + void play(); + void pause(); + void resume(); + void stop(); + void reset(); + void setPlaybackSpeed(float speed); + void setLooping(bool loop); + + bool isPlaying() const; + bool isStopped() const; + + // ------ 事件回调(绑定到主图层)------ + void setKeyframeCallback(KeyframeHitCallback callback); + void setCompletionCallback(AnimationCompleteCallback callback); + void addEventListener(AnimationEventCallback callback); + + // ------ 视觉属性(应用到所有图层)------ + void setTintColor(const Color& color); + void setFlipX(bool flip); + void setFlipY(bool flip); + +private: + struct LayerEntry { + Ptr node; + int zOrder = 0; + }; + std::vector layers_; +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/animation/frame_property.h b/Easy2D/include/easy2d/animation/frame_property.h new file mode 100644 index 0000000..26c9911 --- /dev/null +++ b/Easy2D/include/easy2d/animation/frame_property.h @@ -0,0 +1,194 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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: 裁剪区域 [4个int16] + + // 用户自定义扩展区间 (0x1000+) + UserDefined = 0x1000, +}; + +// ============================================================================ +// 帧属性值 - variant 多态值 +// ============================================================================ +using FramePropertyValue = std::variant< + bool, + int, + float, + std::string, + Vec2, + Color, + std::vector +>; + +// ============================================================================ +// FramePropertyKey 的 hash 支持 +// ============================================================================ +struct FramePropertyKeyHash { + size_t operator()(FramePropertyKey key) const noexcept { + return std::hash{}(static_cast(key)); + } +}; + +// ============================================================================ +// FramePropertySet - 单帧属性集合 +// 同时支持强类型属性和自定义扩展(不固定数据) +// ============================================================================ +class FramePropertySet { +public: + FramePropertySet() = default; + + // ------ 设置属性 ------ + void set(FramePropertyKey key, FramePropertyValue value) { + properties_[key] = std::move(value); + } + + void setCustom(const std::string& key, std::any value) { + customProperties_[key] = std::move(value); + } + + // ------ 类型安全获取 ------ + template + std::optional get(FramePropertyKey key) const { + auto it = properties_.find(key); + if (it == properties_.end()) return std::nullopt; + if (auto* val = std::get_if(&it->second)) { + return *val; + } + return std::nullopt; + } + + template + T getOr(FramePropertyKey key, const T& defaultValue) const { + auto result = get(key); + return result.value_or(defaultValue); + } + + std::optional getCustom(const std::string& key) const { + 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; + 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 customProperties_; +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/animation/frame_renderer.h b/Easy2D/include/easy2d/animation/frame_renderer.h new file mode 100644 index 0000000..feaa187 --- /dev/null +++ b/Easy2D/include/easy2d/animation/frame_renderer.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace easy2d { + +// ============================================================================ +// FrameRenderer - 帧渲染器 +// 单渲染器 + SpriteFrame 引用策略,替代 N帧=N个Sprite 的旧设计 +// 负责预加载帧的 SpriteFrame、渲染当前帧、处理混合模式 +// ============================================================================ +class FrameRenderer { +public: + FrameRenderer() = default; + + // ------ 预加载 ------ + // 解析所有帧的 SpriteFrame(通过 SpriteFrameCache) + bool preloadFrames(const std::vector& frames); + void releaseFrames(); + + // ------ 渲染当前帧 ------ + void renderFrame(RenderBackend& renderer, + const AnimationFrame& frame, + size_t frameIndex, + const Vec2& position, + float nodeOpacity, + const Color& tintColor, + bool flipX, bool flipY); + + // ------ 渲染插值帧 ------ + void renderInterpolated(RenderBackend& renderer, + const AnimationFrame& fromFrame, + size_t fromIndex, + const InterpolatedProperties& props, + const Vec2& position, + float nodeOpacity, + const Color& tintColor, + bool flipX, bool flipY); + + // ------ 混合模式映射 ------ + static BlendMode mapBlendMode(const FramePropertySet& props); + + // ------ 查询 ------ + Ptr getSpriteFrame(size_t frameIndex) const; + Size getMaxFrameSize() const { return maxFrameSize_; } + bool isLoaded() const { return !spriteFrames_.empty(); } + +private: + std::vector> spriteFrames_; + Size maxFrameSize_; + + void drawSpriteFrame(RenderBackend& renderer, + Ptr sf, + const Vec2& position, + const Vec2& offset, + const Vec2& scale, + float rotation, + float opacity, + const Color& tint, + bool flipX, bool flipY, + BlendMode blend); +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/animation/interpolation_engine.h b/Easy2D/include/easy2d/animation/interpolation_engine.h new file mode 100644 index 0000000..a9cf01e --- /dev/null +++ b/Easy2D/include/easy2d/animation/interpolation_engine.h @@ -0,0 +1,105 @@ +#pragma once + +#include +#include +#include +#include +#include + +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 diff --git a/Easy2D/include/easy2d/animation/sprite_frame.h b/Easy2D/include/easy2d/animation/sprite_frame.h new file mode 100644 index 0000000..8dde7a8 --- /dev/null +++ b/Easy2D/include/easy2d/animation/sprite_frame.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include +#include + +namespace easy2d { + +// ============================================================================ +// SpriteFrame - 精灵帧(纹理 + 区域 + 偏移的中间抽象) +// 借鉴 Cocos2d-x SpriteFrame:解耦纹理物理存储与逻辑帧 +// 一个纹理图集可包含多个 SpriteFrame,减少纹理切换提升渲染性能 +// ============================================================================ +class SpriteFrame { +public: + SpriteFrame() = default; + + SpriteFrame(Ptr texture, const Rect& rect) + : texture_(std::move(texture)) + , rect_(rect) + , originalSize_(rect.size) {} + + SpriteFrame(Ptr texture, const Rect& rect, + const Vec2& offset, const Size& originalSize) + : texture_(std::move(texture)) + , rect_(rect) + , offset_(offset) + , originalSize_(originalSize) {} + + // ------ 静态创建 ------ + static Ptr create(Ptr texture, const Rect& rect) { + return makePtr(std::move(texture), rect); + } + + static Ptr create(Ptr texture, const Rect& rect, + const Vec2& offset, const Size& originalSize) { + return makePtr(std::move(texture), rect, offset, originalSize); + } + + // ------ 纹理信息 ------ + void setTexture(Ptr texture) { texture_ = std::move(texture); } + Ptr 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_; + Rect rect_; + Vec2 offset_; + Size originalSize_; + bool rotated_ = false; + std::string name_; +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/animation/sprite_frame_cache.h b/Easy2D/include/easy2d/animation/sprite_frame_cache.h new file mode 100644 index 0000000..27964d2 --- /dev/null +++ b/Easy2D/include/easy2d/animation/sprite_frame_cache.h @@ -0,0 +1,169 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace easy2d { + +// ============================================================================ +// SpriteFrameCache - 精灵帧全局缓存(借鉴 Cocos SpriteFrameCache) +// 全局单例管理所有精灵帧,避免重复创建,支持图集自动切割 +// ============================================================================ +class SpriteFrameCache { +public: + static SpriteFrameCache& getInstance() { + static SpriteFrameCache instance; + return instance; + } + + // ------ 添加帧 ------ + + /// 添加单个精灵帧 + void addSpriteFrame(Ptr frame, const std::string& name) { + std::lock_guard lock(mutex_); + frames_[name] = std::move(frame); + } + + /// 从纹理和矩形区域创建并添加帧 + void addSpriteFrameFromTexture(Ptr 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, 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 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(margin + col * (frameWidth + spacing)), + static_cast(margin + row * (frameHeight + spacing)), + static_cast(frameWidth), + static_cast(frameHeight) + ); + + std::string name = keyPrefix + "#" + std::to_string(i); + auto frame = SpriteFrame::create(texture, rect); + frame->setName(name); + frames_[name] = std::move(frame); + } + } + + // ------ 获取帧 ------ + + /// 按名称获取 + Ptr getSpriteFrame(const std::string& name) const { + std::lock_guard lock(mutex_); + auto it = frames_.find(name); + if (it != frames_.end()) return it->second; + return nullptr; + } + + /// 通过路径+索引获取或创建(ANI 格式的定位方式) + Ptr getOrCreateFromFile(const std::string& texturePath, + int index = 0) { + std::string key = texturePath + "#" + std::to_string(index); + + { + std::lock_guard 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(texture->getWidth()), + static_cast(texture->getHeight())); + + auto frame = SpriteFrame::create(texture, rect); + frame->setName(key); + + std::lock_guard lock(mutex_); + frames_[key] = frame; + return frame; + } + + // ------ 缓存管理 ------ + + bool has(const std::string& name) const { + std::lock_guard lock(mutex_); + return frames_.find(name) != frames_.end(); + } + + void removeSpriteFrame(const std::string& name) { + std::lock_guard lock(mutex_); + frames_.erase(name); + } + + /// 移除未被外部引用的精灵帧(use_count == 1 表示仅缓存自身持有) + void removeUnusedSpriteFrames() { + std::lock_guard 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 lock(mutex_); + frames_.clear(); + } + + size_t count() const { + std::lock_guard 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> frames_; +}; + +// 便捷宏 +#define E2D_SPRITE_FRAME_CACHE() ::easy2d::SpriteFrameCache::getInstance() + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/app/application.h b/Easy2D/include/easy2d/app/application.h new file mode 100644 index 0000000..27788d5 --- /dev/null +++ b/Easy2D/include/easy2d/app/application.h @@ -0,0 +1,127 @@ +#pragma once + +#include +#include +#include +#include +#include + +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 scene); + void enterScene(Ptr scene, Ptr 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_; + UniquePtr renderer_; + UniquePtr sceneManager_; + UniquePtr resourceManager_; + UniquePtr timerManager_; + UniquePtr eventQueue_; + UniquePtr eventDispatcher_; + UniquePtr 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 diff --git a/Easy2D/include/easy2d/audio/audio_engine.h b/Easy2D/include/easy2d/audio/audio_engine.h new file mode 100644 index 0000000..dd12fb4 --- /dev/null +++ b/Easy2D/include/easy2d/audio/audio_engine.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include +#include + +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 loadSound(const std::string& filePath); + std::shared_ptr loadSound(const std::string& name, const std::string& filePath); + + std::shared_ptr 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> sounds_; + float masterVolume_ = 1.0f; + bool initialized_ = false; +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/audio/sound.h b/Easy2D/include/easy2d/audio/sound.h new file mode 100644 index 0000000..f61cc6f --- /dev/null +++ b/Easy2D/include/easy2d/audio/sound.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +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 diff --git a/Easy2D/include/easy2d/core/color.h b/Easy2D/include/easy2d/core/color.h new file mode 100644 index 0000000..ff941db --- /dev/null +++ b/Easy2D/include/easy2d/core/color.h @@ -0,0 +1,131 @@ +#pragma once + +#include +#include +#include + +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((rgb >> 16) & 0xFF) / 255.0f) + , g(static_cast((rgb >> 8) & 0xFF) / 255.0f) + , b(static_cast((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 diff --git a/Easy2D/include/easy2d/core/math_types.h b/Easy2D/include/easy2d/core/math_types.h new file mode 100644 index 0000000..fdd4823 --- /dev/null +++ b/Easy2D/include/easy2d/core/math_types.h @@ -0,0 +1,295 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +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 diff --git a/Easy2D/include/easy2d/core/string.h b/Easy2D/include/easy2d/core/string.h new file mode 100644 index 0000000..7a56d82 --- /dev/null +++ b/Easy2D/include/easy2d/core/string.h @@ -0,0 +1,507 @@ +#pragma once + +#include +#include +#include +#include + +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 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(-1); + + // ------------------------------------------------------------------------ + // 格式化字符串(类似 sprintf) + // ------------------------------------------------------------------------ + template + 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(ch)); + } else if (ch <= 0x10FFFF) { + // Surrogate pair + ch -= 0x10000; + result.push_back(static_cast(0xD800 | (ch >> 10))); + result.push_back(static_cast(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(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(*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(ptr[1]) & 0x3F); + ptr += 2; + } else if ((byte & 0xF0) == 0xE0) { + // 3-byte sequence + ch = (byte & 0x0F) << 12; + ch |= (static_cast(ptr[1]) & 0x3F) << 6; + ch |= (static_cast(ptr[2]) & 0x3F); + ptr += 3; + } else if ((byte & 0xF8) == 0xF0) { + // 4-byte sequence + ch = (byte & 0x07) << 18; + ch |= (static_cast(ptr[1]) & 0x3F) << 12; + ch |= (static_cast(ptr[2]) & 0x3F) << 6; + ch |= (static_cast(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(ch)); + } else if (ch <= 0x7FF) { + // 2-byte + result.push_back(static_cast(0xC0 | ((ch >> 6) & 0x1F))); + result.push_back(static_cast(0x80 | (ch & 0x3F))); + } else if (ch <= 0xFFFF) { + // 3-byte + result.push_back(static_cast(0xE0 | ((ch >> 12) & 0x0F))); + result.push_back(static_cast(0x80 | ((ch >> 6) & 0x3F))); + result.push_back(static_cast(0x80 | (ch & 0x3F))); + } else if (ch <= 0x10FFFF) { + // 4-byte + result.push_back(static_cast(0xF0 | ((ch >> 18) & 0x07))); + result.push_back(static_cast(0x80 | ((ch >> 12) & 0x3F))); + result.push_back(static_cast(0x80 | ((ch >> 6) & 0x3F))); + result.push_back(static_cast(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(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(data_[start]))) { + ++start; + } + + size_t end = data_.size(); + while (end > start && std::isspace(static_cast(data_[end - 1]))) { + --end; + } + + return String(data_.substr(start, end - start)); +} + +inline std::vector String::split(const String& delimiter) const { + std::vector 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 + +template +String String::format(const char* fmt, Args&&... args) { + int size = std::snprintf(nullptr, 0, fmt, std::forward(args)...); + if (size <= 0) return String(); + + std::string result(size, '\0'); + std::snprintf(&result[0], size + 1, fmt, std::forward(args)...); + + return String(result); +} + +// 全局运算符 +inline String operator+(const char* lhs, const String& rhs) { + return String(lhs) + rhs; +} + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/core/types.h b/Easy2D/include/easy2d/core/types.h new file mode 100644 index 0000000..a6445d1 --- /dev/null +++ b/Easy2D/include/easy2d/core/types.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include + +namespace easy2d { + +// --------------------------------------------------------------------------- +// 智能指针别名 +// --------------------------------------------------------------------------- +template +using Ptr = std::shared_ptr; + +template +using UniquePtr = std::unique_ptr; + +template +using WeakPtr = std::weak_ptr; + +/// 创建 shared_ptr 的便捷函数 +template +inline Ptr makePtr(Args&&... args) { + return std::make_shared(std::forward(args)...); +} + +/// 创建 unique_ptr 的便捷函数 +template +inline UniquePtr makeUnique(Args&&... args) { + return std::make_unique(std::forward(args)...); +} + +// --------------------------------------------------------------------------- +// 函数别名 +// --------------------------------------------------------------------------- +template +using Function = std::function; + +// --------------------------------------------------------------------------- +// 基础类型别名 +// --------------------------------------------------------------------------- +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 diff --git a/Easy2D/include/easy2d/easy2d.h b/Easy2D/include/easy2d/easy2d.h new file mode 100644 index 0000000..ae9ecf0 --- /dev/null +++ b/Easy2D/include/easy2d/easy2d.h @@ -0,0 +1,97 @@ +#pragma once + +// Easy2D v3.0 - 统一入口头文件 +// 包含所有公共 API + +// Core +#include +#include +#include +#include + +// Platform +#include +#include + +// Graphics +#include +#include +#include +#include +#include +#include +#include +#include + +// Scene +#include +#include +#include +#include +#include +#include +#include + +// Animation +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// UI +#include +#include + +// Action +#include +#include +#include + +// Event +#include +#include +#include +#include + +// Audio +#include +#include + +// Resource +#include + +// Utils +#include +#include +#include +#include + +// Spatial +#include +#include +#include +#include + +// Effects +#include +#include +#include + +// Application +#include + +// Script +#include +#include diff --git a/Easy2D/include/easy2d/effects/custom_effect_manager.h b/Easy2D/include/easy2d/effects/custom_effect_manager.h new file mode 100644 index 0000000..1d48069 --- /dev/null +++ b/Easy2D/include/easy2d/effects/custom_effect_manager.h @@ -0,0 +1,318 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 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 getEmitter() { return emitter_; } + +private: + Ptr particleSystem_; + Ptr 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 runtimeParams_; +}; + +// ============================================================================ +// 自定义特效工厂 +// ============================================================================ +class CustomEffectFactory { +public: + using EffectCreator = std::function(const CustomEffectConfig&)>; + + static CustomEffectFactory& getInstance(); + + // 注册自定义特效创建器 + void registerEffect(const std::string& typeName, EffectCreator creator); + + // 创建特效 + Ptr create(const std::string& typeName, const CustomEffectConfig& config); + + // 检查是否已注册 + bool isRegistered(const std::string& typeName) const; + + // 获取所有已注册的类型 + std::vector getRegisteredTypes() const; + +private: + CustomEffectFactory() = default; + ~CustomEffectFactory() = default; + CustomEffectFactory(const CustomEffectFactory&) = delete; + CustomEffectFactory& operator=(const CustomEffectFactory&) = delete; + + std::unordered_map 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 getConfigNames() const; + + // ------------------------------------------------------------------------ + // 特效实例管理 + // ------------------------------------------------------------------------ + + /** + * @brief 创建特效实例 + */ + Ptr createEffect(const std::string& name); + + /** + * @brief 从配置直接创建特效 + */ + Ptr createEffectFromConfig(const CustomEffectConfig& config); + + /** + * @brief 销毁特效实例 + */ + void destroyEffect(Ptr effect); + + /** + * @brief 更新所有特效 + */ + void update(float dt); + + /** + * @brief 渲染所有特效 + */ + void render(RenderBackend& renderer); + + /** + * @brief 停止所有特效 + */ + void stopAll(); + + // ------------------------------------------------------------------------ + // 便捷方法 + // ------------------------------------------------------------------------ + + /** + * @brief 播放特效(简写) + */ + Ptr 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 configs_; + std::vector> 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 diff --git a/Easy2D/include/easy2d/effects/particle_system.h b/Easy2D/include/easy2d/effects/particle_system.h new file mode 100644 index 0000000..3db7ceb --- /dev/null +++ b/Easy2D/include/easy2d/effects/particle_system.h @@ -0,0 +1,244 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +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 = 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) { 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 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 create(); + + // ------------------------------------------------------------------------ + // 发射器管理 + // ------------------------------------------------------------------------ + Ptr addEmitter(const EmitterConfig& config = {}); + void removeEmitter(Ptr 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> 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 diff --git a/Easy2D/include/easy2d/effects/post_process.h b/Easy2D/include/easy2d/effects/post_process.h new file mode 100644 index 0000000..c0b8c8f --- /dev/null +++ b/Easy2D/include/easy2d/effects/post_process.h @@ -0,0 +1,225 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +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 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 effect); + + /** + * @brief 插入效果到指定位置 + */ + void insertEffect(size_t index, Ptr effect); + + /** + * @brief 移除效果 + */ + void removeEffect(const std::string& name); + void removeEffect(size_t index); + + /** + * @brief 获取效果 + */ + Ptr getEffect(const std::string& name); + Ptr 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> effects_; + Ptr renderTargetA_; + Ptr 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 diff --git a/Easy2D/include/easy2d/event/event.h b/Easy2D/include/easy2d/event/event.h new file mode 100644 index 0000000..7ea8ad8 --- /dev/null +++ b/Easy2D/include/easy2d/event/event.h @@ -0,0 +1,185 @@ +#pragma once + +#include +#include +#include +#include + +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 diff --git a/Easy2D/include/easy2d/event/event_dispatcher.h b/Easy2D/include/easy2d/event/event_dispatcher.h new file mode 100644 index 0000000..3c5ef1a --- /dev/null +++ b/Easy2D/include/easy2d/event/event_dispatcher.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace easy2d { + +// ============================================================================ +// 事件监听器 ID +// ============================================================================ +using ListenerId = uint64_t; + +// ============================================================================ +// 事件分发器 +// ============================================================================ +class EventDispatcher { +public: + using EventCallback = std::function; + + 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> listeners_; + ListenerId nextId_; +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/event/event_queue.h b/Easy2D/include/easy2d/event/event_queue.h new file mode 100644 index 0000000..3f7ba4b --- /dev/null +++ b/Easy2D/include/easy2d/event/event_queue.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include + +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 queue_; + mutable std::mutex mutex_; +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/event/input_codes.h b/Easy2D/include/easy2d/event/input_codes.h new file mode 100644 index 0000000..96467f3 --- /dev/null +++ b/Easy2D/include/easy2d/event/input_codes.h @@ -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 diff --git a/Easy2D/include/easy2d/graphics/alpha_mask.h b/Easy2D/include/easy2d/graphics/alpha_mask.h new file mode 100644 index 0000000..a299b38 --- /dev/null +++ b/Easy2D/include/easy2d/graphics/alpha_mask.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include + +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(width_), static_cast(height_)); } + + /// 获取原始数据 + const std::vector& getData() const { return data_; } + + /// 检查遮罩是否有效 + bool isValid() const { return !data_.empty() && width_ > 0 && height_ > 0; } + +private: + int width_ = 0; + int height_ = 0; + std::vector data_; // Alpha值数组 +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/graphics/camera.h b/Easy2D/include/easy2d/graphics/camera.h new file mode 100644 index 0000000..2dcc330 --- /dev/null +++ b/Easy2D/include/easy2d/graphics/camera.h @@ -0,0 +1,93 @@ +#pragma once + +#include +#include +#include +#include + +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 diff --git a/Easy2D/include/easy2d/graphics/font.h b/Easy2D/include/easy2d/graphics/font.h new file mode 100644 index 0000000..4fba319 --- /dev/null +++ b/Easy2D/include/easy2d/graphics/font.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include + +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 diff --git a/Easy2D/include/easy2d/graphics/opengl/gl_font_atlas.h b/Easy2D/include/easy2d/graphics/opengl/gl_font_atlas.h new file mode 100644 index 0000000..be3195d --- /dev/null +++ b/Easy2D/include/easy2d/graphics/opengl/gl_font_atlas.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 texture_; + mutable std::unordered_map glyphs_; + + // stb_rect_pack 上下文 + mutable stbrp_context packContext_; + mutable std::vector packNodes_; + mutable int currentY_; + + std::vector fontData_; + stbtt_fontinfo fontInfo_; + float scale_; + float ascent_; + float descent_; + float lineGap_; + + void createAtlas(); + void cacheGlyph(char32_t codepoint) const; +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/graphics/opengl/gl_renderer.h b/Easy2D/include/easy2d/graphics/opengl/gl_renderer.h new file mode 100644 index 0000000..2d07a68 --- /dev/null +++ b/Easy2D/include/easy2d/graphics/opengl/gl_renderer.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include +#include + +// 使用标准 GLES3.2 +#include + +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 createTexture(int width, int height, const uint8_t* pixels, int channels) override; + Ptr 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& points, const Color& color, float width) override; + void fillPolygon(const std::vector& points, const Color& color) override; + + Ptr 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 diff --git a/Easy2D/include/easy2d/graphics/opengl/gl_shader.h b/Easy2D/include/easy2d/graphics/opengl/gl_shader.h new file mode 100644 index 0000000..be37260 --- /dev/null +++ b/Easy2D/include/easy2d/graphics/opengl/gl_shader.h @@ -0,0 +1,56 @@ +#pragma once + +// 使用标准 GLES3.2 +#include + +#include +#include +#include +#include +#include +#include + +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 uniformCache_; + + GLuint compileShader(GLenum type, const char* source); + GLint getUniformLocation(const std::string& name); +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/graphics/opengl/gl_sprite_batch.h b/Easy2D/include/easy2d/graphics/opengl/gl_sprite_batch.h new file mode 100644 index 0000000..d023a67 --- /dev/null +++ b/Easy2D/include/easy2d/graphics/opengl/gl_sprite_batch.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// 使用标准 GLES3.2 +#include + +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 vertices_; + std::vector indices_; + + const Texture* currentTexture_; + bool currentIsSDF_; + glm::mat4 viewProjection_; + + uint32_t drawCallCount_; + uint32_t spriteCount_; + + void flush(); + void setupShader(); +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/graphics/opengl/gl_texture.h b/Easy2D/include/easy2d/graphics/opengl/gl_texture.h new file mode 100644 index 0000000..1faef19 --- /dev/null +++ b/Easy2D/include/easy2d/graphics/opengl/gl_texture.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include + +// 使用标准 GLES3.2 +#include + +#include + +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(width_), static_cast(height_)); } + int getChannels() const override { return channels_; } + PixelFormat getFormat() const override; + void* getNativeHandle() const override { return reinterpret_cast(static_cast(textureID_)); } + bool isValid() const override { return textureID_ != 0; } + void setFilter(bool linear) override; + void setWrap(bool repeat) override; + + // 从参数创建纹理的工厂方法 + static Ptr 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 pixelData_; + std::unique_ptr alphaMask_; + + void createTexture(const uint8_t* pixels); + + // KTX 文件加载 + bool loadKTX(const std::string& filepath); + // DDS 文件加载 + bool loadDDS(const std::string& filepath); +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/graphics/render_backend.h b/Easy2D/include/easy2d/graphics/render_backend.h new file mode 100644 index 0000000..0fd5104 --- /dev/null +++ b/Easy2D/include/easy2d/graphics/render_backend.h @@ -0,0 +1,124 @@ +#pragma once + +#include +#include +#include +#include +#include + +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 createTexture(int width, int height, const uint8_t* pixels, int channels) = 0; + virtual Ptr 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& points, const Color& color, float width = 1.0f) = 0; + virtual void fillPolygon(const std::vector& points, const Color& color) = 0; + + // ------------------------------------------------------------------------ + // 文字渲染 + // ------------------------------------------------------------------------ + virtual Ptr 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 create(BackendType type); +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/graphics/render_command.h b/Easy2D/include/easy2d/graphics/render_command.h new file mode 100644 index 0000000..c36fa49 --- /dev/null +++ b/Easy2D/include/easy2d/graphics/render_command.h @@ -0,0 +1,128 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace easy2d { + +// 前向声明 +class Texture; +class FontAtlas; + +// ============================================================================ +// 渲染命令类型 +// ============================================================================ +enum class RenderCommandType { + Sprite, + Line, + Rect, + FilledRect, + Circle, + FilledCircle, + Triangle, + FilledTriangle, + Polygon, + FilledPolygon, + Text +}; + +// ============================================================================ +// 精灵数据 +// ============================================================================ +struct SpriteData { + Ptr 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 points; + Color color; + float width; +}; + +// ============================================================================ +// 文字数据 +// ============================================================================ +struct TextData { + Ptr 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 diff --git a/Easy2D/include/easy2d/graphics/render_target.h b/Easy2D/include/easy2d/graphics/render_target.h new file mode 100644 index 0000000..6eb555a --- /dev/null +++ b/Easy2D/include/easy2d/graphics/render_target.h @@ -0,0 +1,328 @@ +#pragma once + +#include +#include +#include +#include +#include + +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, 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(width_), static_cast(height_)); } + PixelFormat getColorFormat() const { return colorFormat_; } + + // ------------------------------------------------------------------------ + // 绑定和解绑 + // ------------------------------------------------------------------------ + + /** + * @brief 绑定为当前渲染目标 + */ + void bind(); + + /** + * @brief 解绑(恢复默认渲染目标) + */ + void unbind(); + + /** + * @brief 清除渲染目标 + */ + void clear(const Color& color = Colors::Transparent); + + // ------------------------------------------------------------------------ + // 纹理访问 + // ------------------------------------------------------------------------ + + /** + * @brief 获取颜色纹理 + */ + Ptr getColorTexture() const { return colorTexture_; } + + /** + * @brief 获取深度纹理(如果有) + */ + Ptr 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 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 colorTexture_; // 颜色纹理 + Ptr 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 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 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 defaultRenderTarget_; + std::vector> renderTargets_; + bool initialized_ = false; +}; + +// ============================================================================ +// 便捷宏 +// ============================================================================ +#define E2D_RENDER_TARGET_STACK() ::easy2d::RenderTargetStack::getInstance() +#define E2D_RENDER_TARGET_MANAGER() ::easy2d::RenderTargetManager::getInstance() + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/graphics/shader_preset.h b/Easy2D/include/easy2d/graphics/shader_preset.h new file mode 100644 index 0000000..1006c8e --- /dev/null +++ b/Easy2D/include/easy2d/graphics/shader_preset.h @@ -0,0 +1,319 @@ +#pragma once + +#include +#include +#include +#include + +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 Water(const WaterParams& params); + static Ptr Outline(const OutlineParams& params); + static Ptr Distortion(const DistortionParams& params); + static Ptr Pixelate(const PixelateParams& params); + static Ptr Invert(const InvertParams& params); + static Ptr Grayscale(const GrayscaleParams& params); + static Ptr Blur(const BlurParams& params); + + static Ptr GrayscaleOutline(const GrayscaleParams& grayParams, + const OutlineParams& outlineParams); + static Ptr PixelateInvert(const PixelateParams& pixParams, + const InvertParams& invParams); +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/graphics/shader_system.h b/Easy2D/include/easy2d/graphics/shader_system.h new file mode 100644 index 0000000..e39c30e --- /dev/null +++ b/Easy2D/include/easy2d/graphics/shader_system.h @@ -0,0 +1,179 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace easy2d { + +// ============================================================================ +// Shader参数绑定回调 +// ============================================================================ +using ShaderBindCallback = std::function; + +// ============================================================================ +// Shader系统 - 管理所有Shader的加载、缓存和热重载 +// ============================================================================ +class ShaderSystem { +public: + // ------------------------------------------------------------------------ + // 单例访问 + // ------------------------------------------------------------------------ + static ShaderSystem& getInstance(); + + // ------------------------------------------------------------------------ + // 初始化和关闭 + // ------------------------------------------------------------------------ + bool init(); + void shutdown(); + + // ------------------------------------------------------------------------ + // Shader加载 + // ------------------------------------------------------------------------ + + /** + * @brief 从文件加载Shader + * @param name Shader名称(用于缓存) + * @param vertPath 顶点着色器文件路径 + * @param fragPath 片段着色器文件路径 + * @return 加载的Shader,失败返回nullptr + */ + Ptr loadFromFile(const std::string& name, + const std::string& vertPath, + const std::string& fragPath); + + /** + * @brief 从源码字符串加载Shader + * @param name Shader名称(用于缓存) + * @param vertSource 顶点着色器源码 + * @param fragSource 片段着色器源码 + * @return 加载的Shader,失败返回nullptr + */ + Ptr loadFromSource(const std::string& name, + const std::string& vertSource, + const std::string& fragSource); + + /** + * @brief 从已编译的程序获取Shader + * @param name Shader名称 + * @return 缓存的Shader,不存在返回nullptr + */ + Ptr 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 getBuiltinSpriteShader(); + Ptr getBuiltinParticleShader(); + Ptr getBuiltinPostProcessShader(); + Ptr 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 shader; + std::string vertPath; + std::string fragPath; + uint64_t vertModifiedTime; + uint64_t fragModifiedTime; + bool isBuiltin; + }; + + std::unordered_map shaders_; + bool fileWatching_ = false; + float watchTimer_ = 0.0f; + static constexpr float WATCH_INTERVAL = 1.0f; // 检查间隔(秒) + + // 内置Shader缓存 + Ptr builtinSpriteShader_; + Ptr builtinParticleShader_; + Ptr builtinPostProcessShader_; + Ptr 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 diff --git a/Easy2D/include/easy2d/graphics/texture.h b/Easy2D/include/easy2d/graphics/texture.h new file mode 100644 index 0000000..a81e5fc --- /dev/null +++ b/Easy2D/include/easy2d/graphics/texture.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include + +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 diff --git a/Easy2D/include/easy2d/graphics/texture_pool.h b/Easy2D/include/easy2d/graphics/texture_pool.h new file mode 100644 index 0000000..04fa370 --- /dev/null +++ b/Easy2D/include/easy2d/graphics/texture_pool.h @@ -0,0 +1,194 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +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 get(const std::string& filepath); + + /** + * @brief 异步加载纹理 + * @param filepath 纹理文件路径 + * @param callback 加载完成回调 + */ + void getAsync(const std::string& filepath, + std::function)> callback); + + /** + * @brief 从内存数据创建纹理(自动缓存) + * @param name 纹理名称(用于缓存键) + * @param data 图像数据 + * @param width 宽度 + * @param height 高度 + * @param format 像素格式 + * @return 纹理对象 + */ + Ptr 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); + + /** + * @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; + size_t size; // 纹理大小(字节) + float lastAccessTime; // 最后访问时间 + uint32_t accessCount; // 访问次数 + }; + + // LRU列表(最近使用的在前面) + using LRUList = std::list; + using LRUIterator = LRUList::iterator; + + mutable std::mutex mutex_; + TexturePoolConfig config_; + + // 缓存存储 + std::unordered_map cache_; + std::unordered_map 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)> callback; + }; + std::vector asyncTasks_; + + // 内部方法 + void touch(const std::string& key); + void evict(); + Ptr 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 diff --git a/Easy2D/include/easy2d/graphics/vram_manager.h b/Easy2D/include/easy2d/graphics/vram_manager.h new file mode 100644 index 0000000..98f4da6 --- /dev/null +++ b/Easy2D/include/easy2d/graphics/vram_manager.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include + +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 diff --git a/Easy2D/include/easy2d/platform/input.h b/Easy2D/include/easy2d/platform/input.h new file mode 100644 index 0000000..ae7e87c --- /dev/null +++ b/Easy2D/include/easy2d/platform/input.h @@ -0,0 +1,114 @@ +#pragma once + +#include +#include +#include + +#include + +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 buttonsDown_; + std::array 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 diff --git a/Easy2D/include/easy2d/platform/switch_compat.h b/Easy2D/include/easy2d/platform/switch_compat.h new file mode 100644 index 0000000..3a8b8f1 --- /dev/null +++ b/Easy2D/include/easy2d/platform/switch_compat.h @@ -0,0 +1,79 @@ +#pragma once + +/** + * @file switch_compat.h + * @brief Nintendo Switch 兼容性头文件 + * + * 提供 Switch 平台特定的兼容性定义和宏 + */ + +#ifdef __SWITCH__ + +// ============================================================================ +// Switch 平台包含 +// ============================================================================ +#include +#include +#include +#include +#include +#include +#include + +// ============================================================================ +// 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__ diff --git a/Easy2D/include/easy2d/platform/window.h b/Easy2D/include/easy2d/platform/window.h new file mode 100644 index 0000000..1d3adaa --- /dev/null +++ b/Easy2D/include/easy2d/platform/window.h @@ -0,0 +1,138 @@ +#pragma once + +#include +#include +#include +#include + +#include + +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(width_), static_cast(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; + using FocusCallback = std::function; + using CloseCallback = std::function; + + 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_; + + ResizeCallback resizeCallback_; + FocusCallback focusCallback_; + CloseCallback closeCallback_; + + bool initSDL(); + void deinitSDL(); +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/resource/resource_manager.h b/Easy2D/include/easy2d/resource/resource_manager.h new file mode 100644 index 0000000..b89ba7a --- /dev/null +++ b/Easy2D/include/easy2d/resource/resource_manager.h @@ -0,0 +1,151 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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& getSearchPaths() const { return searchPaths_; } + + /// 查找资源文件完整路径 + std::string findResourcePath(const std::string& filename) const; + + // ------------------------------------------------------------------------ + // 纹理资源 + // ------------------------------------------------------------------------ + + /// 加载纹理(带缓存) + Ptr loadTexture(const std::string& filepath); + + /// 加载纹理并生成Alpha遮罩(用于不规则形状图片) + Ptr loadTextureWithAlphaMask(const std::string& filepath); + + /// 通过key获取已缓存的纹理 + Ptr 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 loadFont(const std::string& filepath, int fontSize, bool useSDF = false); + + /// 通过key获取已缓存的字体图集 + Ptr getFont(const std::string& key) const; + + /// 检查字体是否已缓存 + bool hasFont(const std::string& key) const; + + /// 卸载指定字体 + void unloadFont(const std::string& key); + + // ------------------------------------------------------------------------ + // 音效资源 + // ------------------------------------------------------------------------ + + /// 加载音效(带缓存) + Ptr loadSound(const std::string& filepath); + Ptr loadSound(const std::string& name, const std::string& filepath); + + /// 通过key获取已缓存的音效 + Ptr 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 searchPaths_; + + // 资源缓存 - 使用弱指针实现自动清理 + std::unordered_map> textureCache_; + std::unordered_map> fontCache_; + std::unordered_map> soundCache_; +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/scene/node.h b/Easy2D/include/easy2d/scene/node.h new file mode 100644 index 0000000..1982399 --- /dev/null +++ b/Easy2D/include/easy2d/scene/node.h @@ -0,0 +1,194 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace easy2d { + +// 前向声明 +class Scene; +class Action; +class RenderBackend; +struct RenderCommand; + +// ============================================================================ +// 节点基类 - 场景图的基础 +// ============================================================================ +class Node : public std::enable_shared_from_this { +public: + Node(); + virtual ~Node(); + + // ------------------------------------------------------------------------ + // 层级管理 + // ------------------------------------------------------------------------ + void addChild(Ptr child); + void removeChild(Ptr child); + void removeChildByName(const std::string& name); + void removeFromParent(); + void removeAllChildren(); + + Ptr getParent() const { return parent_.lock(); } + const std::vector>& getChildren() const { return children_; } + Ptr getChildByName(const std::string& name) const; + Ptr 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); + void stopAllActions(); + void stopAction(Ptr action); + void stopActionByTag(int tag); + Ptr 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& commands, int parentZOrder = 0); + +protected: + // 子类重写 + virtual void onDraw(RenderBackend& renderer) {} + virtual void onUpdateNode(float dt) {} + virtual void generateRenderCommand(std::vector& 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 parent_; + std::vector> 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> actions_; + + // 事件 + EventDispatcher eventDispatcher_; +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/scene/scene.h b/Easy2D/include/easy2d/scene/scene.h new file mode 100644 index 0000000..3ec233d --- /dev/null +++ b/Easy2D/include/easy2d/scene/scene.h @@ -0,0 +1,107 @@ +#pragma once + +#include +#include +#include +#include +#include + +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); + Ptr 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& 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 queryNodesInArea(const Rect& area) const; + std::vector queryNodesAtPoint(const Vec2& point) const; + std::vector> queryCollisions() const; + + // ------------------------------------------------------------------------ + // 静态创建方法 + // ------------------------------------------------------------------------ + static Ptr create(); + +protected: + void onEnter() override; + void onExit() override; + + friend class SceneManager; + +private: + Color backgroundColor_ = Colors::Black; + Size viewportSize_ = Size::Zero(); + + Ptr camera_; + Ptr defaultCamera_; + + bool paused_ = false; + + // 空间索引系统 + SpatialManager spatialManager_; + bool spatialIndexingEnabled_ = true; +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/scene/scene_manager.h b/Easy2D/include/easy2d/scene/scene_manager.h new file mode 100644 index 0000000..29b3b41 --- /dev/null +++ b/Easy2D/include/easy2d/scene/scene_manager.h @@ -0,0 +1,147 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace easy2d { + +// 前向声明 +struct RenderCommand; +class Transition; + +// ============================================================================ +// 场景切换特效类型 +// ============================================================================ +enum class TransitionType { + None, + Fade, + SlideLeft, + SlideRight, + SlideUp, + SlideDown, + Scale, + Flip +}; + +// ============================================================================ +// 场景管理器 - 管理场景的生命周期和切换 +// ============================================================================ +class SceneManager { +public: + using TransitionCallback = std::function; + + // ------------------------------------------------------------------------ + // 单例访问 + // ------------------------------------------------------------------------ + static SceneManager& getInstance(); + + // ------------------------------------------------------------------------ + // 场景栈操作 + // ------------------------------------------------------------------------ + + // 运行第一个场景 + void runWithScene(Ptr scene); + + // 替换当前场景 + void replaceScene(Ptr scene); + void replaceScene(Ptr scene, TransitionType transition, float duration = 0.5f); + + // 压入新场景(当前场景暂停) + void pushScene(Ptr scene); + void pushScene(Ptr 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 getCurrentScene() const; + Ptr getPreviousScene() const; + Ptr getRootScene() const; + + // 通过名称获取场景 + Ptr 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& 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); + void enterScene(Ptr scene, Ptr transition); + +private: + void doSceneSwitch(); + void startTransition(Ptr from, Ptr to, TransitionType type, float duration, Function stackAction); + void updateTransition(float dt); + void finishTransition(); + void dispatchPointerEvents(Scene& scene); + + std::stack> sceneStack_; + std::unordered_map> namedScenes_; + + // Transition state + bool isTransitioning_ = false; + TransitionType currentTransition_ = TransitionType::None; + float transitionDuration_ = 0.0f; + float transitionElapsed_ = 0.0f; + Ptr outgoingScene_; + Ptr incomingScene_; + Ptr activeTransition_; + Function transitionStackAction_; + TransitionCallback transitionCallback_; + + // Next scene to switch to (queued during transition) + Ptr nextScene_; + bool sendCleanupToScene_ = false; + + Node* hoverTarget_ = nullptr; + Node* captureTarget_ = nullptr; + Vec2 lastPointerWorld_ = Vec2::Zero(); + bool hasLastPointerWorld_ = false; +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/scene/shape_node.h b/Easy2D/include/easy2d/scene/shape_node.h new file mode 100644 index 0000000..8159dbf --- /dev/null +++ b/Easy2D/include/easy2d/scene/shape_node.h @@ -0,0 +1,107 @@ +#pragma once + +#include +#include +#include +#include + +namespace easy2d { + +// ============================================================================ +// 形状类型 +// ============================================================================ +enum class ShapeType { + Point, + Line, + Rect, + Circle, + Triangle, + Polygon +}; + +// ============================================================================ +// 形状节点 - 用于绘制几何形状 +// ============================================================================ +class ShapeNode : public Node { +public: + ShapeNode(); + ~ShapeNode() override = default; + + // ------------------------------------------------------------------------ + // 静态创建方法 + // ------------------------------------------------------------------------ + static Ptr create(); + + // 点 + static Ptr createPoint(const Vec2& pos, const Color& color); + + // 线 + static Ptr createLine(const Vec2& start, const Vec2& end, + const Color& color, float width = 1.0f); + + // 矩形 + static Ptr createRect(const Rect& rect, const Color& color, + float width = 1.0f); + static Ptr createFilledRect(const Rect& rect, const Color& color); + + // 圆形 + static Ptr createCircle(const Vec2& center, float radius, + const Color& color, int segments = 32, + float width = 1.0f); + static Ptr createFilledCircle(const Vec2& center, float radius, + const Color& color, int segments = 32); + + // 三角形 + static Ptr createTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3, + const Color& color, float width = 1.0f); + static Ptr createFilledTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3, + const Color& color); + + // 多边形 + static Ptr createPolygon(const std::vector& points, + const Color& color, float width = 1.0f); + static Ptr createFilledPolygon(const std::vector& points, + const Color& color); + + // ------------------------------------------------------------------------ + // 属性设置 + // ------------------------------------------------------------------------ + void setShapeType(ShapeType type) { shapeType_ = type; } + ShapeType getShapeType() const { return shapeType_; } + + void setColor(const Color& color) { color_ = color; } + Color getColor() const { return color_; } + + void setFilled(bool filled) { filled_ = filled; } + bool isFilled() const { return filled_; } + + void setLineWidth(float width) { lineWidth_ = width; } + float getLineWidth() const { return lineWidth_; } + + void setSegments(int segments) { segments_ = segments; } + int getSegments() const { return segments_; } + + // ------------------------------------------------------------------------ + // 点设置 + // ------------------------------------------------------------------------ + void setPoints(const std::vector& points); + const std::vector& getPoints() const { return points_; } + void addPoint(const Vec2& point); + void clearPoints(); + + Rect getBoundingBox() const override; + +protected: + void onDraw(RenderBackend& renderer) override; + void generateRenderCommand(std::vector& commands, int zOrder) override; + +private: + ShapeType shapeType_ = ShapeType::Rect; + Color color_ = Colors::White; + bool filled_ = false; + float lineWidth_ = 1.0f; + int segments_ = 32; + std::vector points_; +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/scene/sprite.h b/Easy2D/include/easy2d/scene/sprite.h new file mode 100644 index 0000000..b8bd22d --- /dev/null +++ b/Easy2D/include/easy2d/scene/sprite.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include + +namespace easy2d { + +// ============================================================================ +// 精灵节点 +// ============================================================================ +class Sprite : public Node { +public: + Sprite(); + explicit Sprite(Ptr texture); + ~Sprite() override = default; + + // 纹理 + void setTexture(Ptr texture); + Ptr getTexture() const { return texture_; } + + // 纹理矩形 (用于图集) + void setTextureRect(const Rect& rect); + Rect getTextureRect() const { return textureRect_; } + + // 颜色混合 + void setColor(const Color& color); + Color getColor() const { return color_; } + + // 翻转 + void setFlipX(bool flip); + void setFlipY(bool flip); + bool isFlipX() const { return flipX_; } + bool isFlipY() const { return flipY_; } + + // 静态创建方法 + static Ptr create(); + static Ptr create(Ptr texture); + static Ptr create(Ptr texture, const Rect& rect); + + Rect getBoundingBox() const override; + +protected: + void onDraw(RenderBackend& renderer) override; + void generateRenderCommand(std::vector& commands, int zOrder) override; + +private: + Ptr texture_; + Rect textureRect_; + Color color_ = Colors::White; + bool flipX_ = false; + bool flipY_ = false; +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/scene/text.h b/Easy2D/include/easy2d/scene/text.h new file mode 100644 index 0000000..cecbf60 --- /dev/null +++ b/Easy2D/include/easy2d/scene/text.h @@ -0,0 +1,84 @@ +#pragma once + +#include +#include +#include +#include + +namespace easy2d { + +// ============================================================================ +// 文字节点 +// ============================================================================ +class Text : public Node { +public: + Text(); + explicit Text(const String& text); + ~Text() override = default; + + // ------------------------------------------------------------------------ + // 文字内容 + // ------------------------------------------------------------------------ + void setText(const String& text); + const String& getText() const { return text_; } + + // ------------------------------------------------------------------------ + // 字体 + // ------------------------------------------------------------------------ + void setFont(Ptr font); + Ptr getFont() const { return font_; } + + // ------------------------------------------------------------------------ + // 文字属性 + // ------------------------------------------------------------------------ + void setTextColor(const Color& color); + Color getTextColor() const { return color_; } + + void setFontSize(int size); + int getFontSize() const { return fontSize_; } + + // ------------------------------------------------------------------------ + // 对齐方式 + // ------------------------------------------------------------------------ + enum class Alignment { + Left, + Center, + Right + }; + + void setAlignment(Alignment align); + Alignment getAlignment() const { return alignment_; } + + // ------------------------------------------------------------------------ + // 尺寸计算 + // ------------------------------------------------------------------------ + Vec2 getTextSize() const; + float getLineHeight() const; + + // ------------------------------------------------------------------------ + // 静态创建方法 + // ------------------------------------------------------------------------ + static Ptr create(); + static Ptr create(const String& text); + static Ptr create(const String& text, Ptr font); + + Rect getBoundingBox() const override; + +protected: + void onDraw(RenderBackend& renderer) override; + void generateRenderCommand(std::vector& commands, int zOrder) override; + +private: + String text_; + Ptr font_; + Color color_ = Colors::White; + int fontSize_ = 16; + Alignment alignment_ = Alignment::Left; + + mutable Vec2 cachedSize_ = Vec2::Zero(); + mutable bool sizeDirty_ = true; + + void updateCache() const; +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/scene/transition.h b/Easy2D/include/easy2d/scene/transition.h new file mode 100644 index 0000000..2352955 --- /dev/null +++ b/Easy2D/include/easy2d/scene/transition.h @@ -0,0 +1,140 @@ +#pragma once + +#include +#include +#include +#include + +namespace easy2d { + +// ============================================================================ +// 过渡方向 +// ============================================================================ +enum class TransitionDirection { + Left, + Right, + Up, + Down +}; + +// ============================================================================ +// 过渡效果基类 +// ============================================================================ +class Transition : public std::enable_shared_from_this { +public: + using FinishCallback = std::function; + + Transition(float duration); + virtual ~Transition() = default; + + // 开始过渡 + void start(Ptr from, Ptr to); + + // 更新过渡进度 + void update(float dt); + + // 渲染过渡效果 + virtual void render(RenderBackend& renderer); + + // 是否完成 + bool isFinished() const { return isFinished_; } + + // 获取进度 [0, 1] + float getProgress() const { return progress_; } + + // 获取淡入淡出进度 (0->1 for fade in, 1->0 for fade out) + float getFadeInAlpha() const; + float getFadeOutAlpha() const; + + // 设置完成回调 + void setFinishCallback(FinishCallback callback) { finishCallback_ = callback; } + + // 获取源场景和目标场景 + Ptr getOutgoingScene() const { return outgoingScene_; } + Ptr getIncomingScene() const { return incomingScene_; } + +protected: + // 子类实现具体的渲染效果 + virtual void onRenderTransition(RenderBackend& renderer, float progress) = 0; + + // 过渡完成时调用 + virtual void onFinish(); + + float duration_; + float elapsed_; + float progress_; + bool isFinished_; + bool isStarted_; + + Ptr outgoingScene_; + Ptr incomingScene_; + FinishCallback finishCallback_; +}; + +// ============================================================================ +// 淡入淡出过渡 +// ============================================================================ +class FadeTransition : public Transition { +public: + FadeTransition(float duration); + +protected: + void onRenderTransition(RenderBackend& renderer, float progress) override; +}; + +// ============================================================================ +// 滑动过渡 +// ============================================================================ +class SlideTransition : public Transition { +public: + SlideTransition(float duration, TransitionDirection direction); + +protected: + void onRenderTransition(RenderBackend& renderer, float progress) override; + +private: + TransitionDirection direction_; +}; + +// ============================================================================ +// 缩放过渡 +// ============================================================================ +class ScaleTransition : public Transition { +public: + ScaleTransition(float duration); + +protected: + void onRenderTransition(RenderBackend& renderer, float progress) override; +}; + +// ============================================================================ +// 翻页过渡 +// ============================================================================ +class FlipTransition : public Transition { +public: + enum class Axis { Horizontal, Vertical }; + + FlipTransition(float duration, Axis axis = Axis::Horizontal); + +protected: + void onRenderTransition(RenderBackend& renderer, float progress) override; + +private: + Axis axis_; +}; + +// ============================================================================ +// 马赛克/方块过渡 +// ============================================================================ +class BoxTransition : public Transition { +public: + BoxTransition(float duration, int divisions = 8); + +protected: + void onRenderTransition(RenderBackend& renderer, float progress) override; + +private: + int divisions_; +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/script/script_engine.h b/Easy2D/include/easy2d/script/script_engine.h new file mode 100644 index 0000000..55ff2de --- /dev/null +++ b/Easy2D/include/easy2d/script/script_engine.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +namespace easy2d { + +class ScriptEngine { +public: + static ScriptEngine& getInstance(); + + ScriptEngine(const ScriptEngine&) = delete; + ScriptEngine& operator=(const ScriptEngine&) = delete; + + bool initialize(); + void shutdown(); + + bool executeString(const std::string& code); + bool executeFile(const std::string& filepath); + + HSQUIRRELVM getVM() const { return vm_; } + bool isInitialized() const { return vm_ != nullptr; } + +private: + ScriptEngine() = default; + ~ScriptEngine(); + + bool compileAndRun(const std::string& source, const std::string& sourceName); + + static void printFunc(HSQUIRRELVM vm, const SQChar* fmt, ...); + static void errorFunc(HSQUIRRELVM vm, const SQChar* fmt, ...); + static SQInteger errorHandler(HSQUIRRELVM vm); + static void compilerError(HSQUIRRELVM vm, const SQChar* desc, + const SQChar* source, SQInteger line, SQInteger column); + + HSQUIRRELVM vm_ = nullptr; +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/script/script_node.h b/Easy2D/include/easy2d/script/script_node.h new file mode 100644 index 0000000..fe94c75 --- /dev/null +++ b/Easy2D/include/easy2d/script/script_node.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include + +namespace easy2d { + +class ScriptNode : public Node { +public: + ScriptNode(); + ~ScriptNode() override; + + static Ptr create(const std::string& scriptPath); + + bool loadScript(const std::string& scriptPath); + const std::string& getScriptPath() const { return scriptPath_; } + + void onEnter() override; + void onExit() override; + void onUpdate(float dt) override; + +private: + bool callMethod(const char* name); + bool callMethodWithFloat(const char* name, float arg); + void pushSelf(); + + std::string scriptPath_; + HSQOBJECT scriptTable_; + bool tableValid_ = false; +}; + +} // namespace easy2d diff --git a/Easy2D/include/easy2d/script/sq_binding.h b/Easy2D/include/easy2d/script/sq_binding.h new file mode 100644 index 0000000..db12b04 --- /dev/null +++ b/Easy2D/include/easy2d/script/sq_binding.h @@ -0,0 +1,252 @@ +#pragma once + +#include +#include +#include + +namespace easy2d { +namespace sq { + +// ============================================================================ +// Type tag helpers — unique address per type +// ============================================================================ +template +inline SQUserPointer typeTag() { + static int tag; + return &tag; +} + +// ============================================================================ +// SqClassName trait — maps C++ types to Squirrel class names +// ============================================================================ +template +struct SqClassName { + static const char* name(); +}; + +// ============================================================================ +// push / get primitives +// ============================================================================ +inline void push(HSQUIRRELVM vm, int v) { sq_pushinteger(vm, static_cast(v)); } +inline void push(HSQUIRRELVM vm, float v) { sq_pushfloat(vm, static_cast(v)); } +inline void push(HSQUIRRELVM vm, double v) { sq_pushfloat(vm, static_cast(v)); } +inline void push(HSQUIRRELVM vm, bool v) { sq_pushbool(vm, v ? SQTrue : SQFalse); } +inline void push(HSQUIRRELVM vm, const char* v) { sq_pushstring(vm, v, -1); } +inline void push(HSQUIRRELVM vm, const std::string& v) { sq_pushstring(vm, v.c_str(), static_cast(v.size())); } +inline void pushNull(HSQUIRRELVM vm) { sq_pushnull(vm); } + +inline SQInteger getInt(HSQUIRRELVM vm, SQInteger idx) { + SQInteger val = 0; + sq_getinteger(vm, idx, &val); + return val; +} + +inline SQFloat getFloat(HSQUIRRELVM vm, SQInteger idx) { + SQFloat val = 0; + if (SQ_FAILED(sq_getfloat(vm, idx, &val))) { + SQInteger ival = 0; + if (SQ_SUCCEEDED(sq_getinteger(vm, idx, &ival))) + val = static_cast(ival); + } + return val; +} + +inline bool getBool(HSQUIRRELVM vm, SQInteger idx) { + SQBool val = SQFalse; + sq_getbool(vm, idx, &val); + return val != SQFalse; +} + +inline std::string getString(HSQUIRRELVM vm, SQInteger idx) { + const SQChar* str = nullptr; + sq_getstring(vm, idx, &str); + return str ? std::string(str) : std::string(); +} + +// ============================================================================ +// Value type userdata helpers +// ============================================================================ +template +T* pushValueInstance(HSQUIRRELVM vm, const T& val) { + sq_pushroottable(vm); + sq_pushstring(vm, SqClassName::name(), -1); + if (SQ_FAILED(sq_get(vm, -2))) { + sq_pop(vm, 1); + return nullptr; + } + if (SQ_FAILED(sq_createinstance(vm, -1))) { + sq_pop(vm, 2); + return nullptr; + } + + T* ud = nullptr; + sq_getinstanceup(vm, -1, reinterpret_cast(&ud), nullptr, SQFalse); + if (ud) *ud = val; + + sq_remove(vm, -2); // class + sq_remove(vm, -2); // roottable + return ud; +} + +template +T* getValueInstance(HSQUIRRELVM vm, SQInteger idx) { + T* ud = nullptr; + sq_getinstanceup(vm, idx, reinterpret_cast(&ud), typeTag(), SQFalse); + return ud; +} + +// ============================================================================ +// Shared pointer bridge for reference types +// ============================================================================ +template +void pushPtr(HSQUIRRELVM vm, Ptr ptr) { + if (!ptr) { sq_pushnull(vm); return; } + + sq_pushroottable(vm); + sq_pushstring(vm, SqClassName::name(), -1); + if (SQ_FAILED(sq_get(vm, -2))) { + sq_pop(vm, 1); + sq_pushnull(vm); + return; + } + if (SQ_FAILED(sq_createinstance(vm, -1))) { + sq_pop(vm, 2); + sq_pushnull(vm); + return; + } + + auto* storage = new Ptr(std::move(ptr)); + sq_setinstanceup(vm, -1, storage); + sq_setreleasehook(vm, -1, [](SQUserPointer p, SQInteger) -> SQInteger { + delete static_cast*>(p); + return 0; + }); + + sq_remove(vm, -2); // class + sq_remove(vm, -2); // roottable +} + +template +Ptr getPtr(HSQUIRRELVM vm, SQInteger idx) { + SQUserPointer up = nullptr; + sq_getinstanceup(vm, idx, &up, typeTag(), SQFalse); + if (!up) return nullptr; + return *static_cast*>(up); +} + +template +T* getRawPtr(HSQUIRRELVM vm, SQInteger idx) { + auto p = getPtr(vm, idx); + return p ? p.get() : nullptr; +} + +// ============================================================================ +// Singleton pointer (no release hook) +// ============================================================================ +template +void pushSingleton(HSQUIRRELVM vm, T* ptr, const char* className) { + sq_pushroottable(vm); + sq_pushstring(vm, className, -1); + if (SQ_FAILED(sq_get(vm, -2))) { + sq_pop(vm, 1); + sq_pushnull(vm); + return; + } + if (SQ_FAILED(sq_createinstance(vm, -1))) { + sq_pop(vm, 2); + sq_pushnull(vm); + return; + } + sq_setinstanceup(vm, -1, ptr); + sq_remove(vm, -2); // class + sq_remove(vm, -2); // roottable +} + +template +T* getSingleton(HSQUIRRELVM vm, SQInteger idx) { + SQUserPointer up = nullptr; + sq_getinstanceup(vm, idx, &up, nullptr, SQFalse); + return static_cast(up); +} + +// ============================================================================ +// ClassDef — fluent API for registering a class +// ============================================================================ +struct ClassDef { + HSQUIRRELVM vm; + + ClassDef(HSQUIRRELVM v, const char* name, const char* base = nullptr) + : vm(v) { + sq_pushroottable(vm); + sq_pushstring(vm, name, -1); + + if (base) { + sq_pushstring(vm, base, -1); + if (SQ_FAILED(sq_get(vm, -3))) { + sq_newclass(vm, SQFalse); + } else { + sq_newclass(vm, SQTrue); + } + } else { + sq_newclass(vm, SQFalse); + } + } + + ClassDef& setTypeTag(SQUserPointer tag) { + sq_settypetag(vm, -1, tag); + return *this; + } + + ClassDef& method(const char* name, SQFUNCTION fn, SQInteger nparams = 0, const char* typemask = nullptr) { + sq_pushstring(vm, name, -1); + sq_newclosure(vm, fn, 0); + if (nparams > 0 && typemask) + sq_setparamscheck(vm, nparams, typemask); + sq_newslot(vm, -3, SQFalse); + return *this; + } + + ClassDef& staticMethod(const char* name, SQFUNCTION fn, SQInteger nparams = 0, const char* typemask = nullptr) { + sq_pushstring(vm, name, -1); + sq_newclosure(vm, fn, 0); + if (nparams > 0 && typemask) + sq_setparamscheck(vm, nparams, typemask); + sq_newslot(vm, -3, SQTrue); + return *this; + } + + template + ClassDef& setValueType(SQFUNCTION constructor) { + sq_settypetag(vm, -1, typeTag()); + sq_setclassudsize(vm, -1, sizeof(T)); + sq_pushstring(vm, "constructor", -1); + sq_newclosure(vm, constructor, 0); + sq_newslot(vm, -3, SQFalse); + return *this; + } + + void commit() { + sq_newslot(vm, -3, SQFalse); + sq_pop(vm, 1); + } +}; + +// ============================================================================ +// Register a table of integer constants +// ============================================================================ +inline void registerConstTable(HSQUIRRELVM vm, const char* tableName, + const char* const* names, const SQInteger* values, int count) { + sq_pushroottable(vm); + sq_pushstring(vm, tableName, -1); + sq_newtable(vm); + for (int i = 0; i < count; ++i) { + sq_pushstring(vm, names[i], -1); + sq_pushinteger(vm, values[i]); + sq_newslot(vm, -3, SQFalse); + } + sq_newslot(vm, -3, SQFalse); + sq_pop(vm, 1); +} + +} // namespace sq +} // namespace easy2d diff --git a/Easy2D/include/easy2d/script/sq_binding_action.h b/Easy2D/include/easy2d/script/sq_binding_action.h new file mode 100644 index 0000000..b56099d --- /dev/null +++ b/Easy2D/include/easy2d/script/sq_binding_action.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace easy2d { +namespace sq { + +void registerActionBindings(HSQUIRRELVM vm); + +} // namespace sq +} // namespace easy2d diff --git a/Easy2D/include/easy2d/script/sq_binding_animation.h b/Easy2D/include/easy2d/script/sq_binding_animation.h new file mode 100644 index 0000000..7a7e934 --- /dev/null +++ b/Easy2D/include/easy2d/script/sq_binding_animation.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace easy2d { +namespace sq { + +void registerAnimationBindings(HSQUIRRELVM vm); + +} // namespace sq +} // namespace easy2d diff --git a/Easy2D/include/easy2d/script/sq_binding_audio.h b/Easy2D/include/easy2d/script/sq_binding_audio.h new file mode 100644 index 0000000..3f99730 --- /dev/null +++ b/Easy2D/include/easy2d/script/sq_binding_audio.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace easy2d { +namespace sq { + +void registerAudioBindings(HSQUIRRELVM vm); + +} // namespace sq +} // namespace easy2d diff --git a/Easy2D/include/easy2d/script/sq_binding_input.h b/Easy2D/include/easy2d/script/sq_binding_input.h new file mode 100644 index 0000000..d34a386 --- /dev/null +++ b/Easy2D/include/easy2d/script/sq_binding_input.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace easy2d { +namespace sq { + +void registerInput(HSQUIRRELVM vm); +void registerKeyConstants(HSQUIRRELVM vm); +void registerInputBindings(HSQUIRRELVM vm); + +} // namespace sq +} // namespace easy2d diff --git a/Easy2D/include/easy2d/script/sq_binding_node.h b/Easy2D/include/easy2d/script/sq_binding_node.h new file mode 100644 index 0000000..5e3f174 --- /dev/null +++ b/Easy2D/include/easy2d/script/sq_binding_node.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace easy2d { +namespace sq { + +void registerNode(HSQUIRRELVM vm); +void registerSprite(HSQUIRRELVM vm); +void registerScene(HSQUIRRELVM vm); +void registerSceneManager(HSQUIRRELVM vm); +void registerApplication(HSQUIRRELVM vm); +void registerNodeBindings(HSQUIRRELVM vm); + +} // namespace sq +} // namespace easy2d diff --git a/Easy2D/include/easy2d/script/sq_binding_types.h b/Easy2D/include/easy2d/script/sq_binding_types.h new file mode 100644 index 0000000..0dd3b47 --- /dev/null +++ b/Easy2D/include/easy2d/script/sq_binding_types.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include + +namespace easy2d { +namespace sq { + +// SqClassName specializations for value types +template<> struct SqClassName { static const char* name() { return "Vec2"; } }; +template<> struct SqClassName { static const char* name() { return "Size"; } }; +template<> struct SqClassName { static const char* name() { return "Rect"; } }; +template<> struct SqClassName { static const char* name() { return "Color"; } }; + +void registerVec2(HSQUIRRELVM vm); +void registerSize(HSQUIRRELVM vm); +void registerRect(HSQUIRRELVM vm); +void registerColor(HSQUIRRELVM vm); +void registerValueTypes(HSQUIRRELVM vm); + +} // namespace sq +} // namespace easy2d diff --git a/Easy2D/include/easy2d/spatial/quadtree.h b/Easy2D/include/easy2d/spatial/quadtree.h new file mode 100644 index 0000000..4d93a44 --- /dev/null +++ b/Easy2D/include/easy2d/spatial/quadtree.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include + +namespace easy2d { + +class QuadTree : public ISpatialIndex { +public: + static constexpr int MAX_OBJECTS = 10; + static constexpr int MAX_LEVELS = 5; + + struct QuadTreeNode { + Rect bounds; + int level; + std::vector> objects; + std::array, 4> children; + + QuadTreeNode(const Rect& bounds, int level); + bool contains(const Rect& rect) const; + bool intersects(const Rect& rect) const; + }; + + explicit QuadTree(const Rect& worldBounds); + ~QuadTree() override = default; + + void insert(Node* node, const Rect& bounds) override; + void remove(Node* node) override; + void update(Node* node, const Rect& newBounds) override; + + std::vector query(const Rect& area) const override; + std::vector query(const Vec2& point) const override; + std::vector> queryCollisions() const override; + + void clear() override; + size_t size() const override; + bool empty() const override; + + void rebuild() override; + +private: + void split(QuadTreeNode* node); + void insertIntoNode(QuadTreeNode* node, Node* object, const Rect& bounds); + void queryNode(const QuadTreeNode* node, const Rect& area, std::vector& results) const; + void queryNode(const QuadTreeNode* node, const Vec2& point, std::vector& results) const; + void collectCollisions(const QuadTreeNode* node, std::vector>& collisions) const; + bool removeFromNode(QuadTreeNode* node, Node* object); + + std::unique_ptr root_; + Rect worldBounds_; + size_t objectCount_ = 0; +}; + +} diff --git a/Easy2D/include/easy2d/spatial/spatial_hash.h b/Easy2D/include/easy2d/spatial/spatial_hash.h new file mode 100644 index 0000000..667756a --- /dev/null +++ b/Easy2D/include/easy2d/spatial/spatial_hash.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include + +namespace easy2d { + +class SpatialHash : public ISpatialIndex { +public: + using CellKey = std::pair; + + struct CellKeyHash { + size_t operator()(const CellKey& key) const { + return std::hash()(key.first) ^ (std::hash()(key.second) << 1); + } + }; + + explicit SpatialHash(float cellSize = 64.0f); + ~SpatialHash() override = default; + + void insert(Node* node, const Rect& bounds) override; + void remove(Node* node) override; + void update(Node* node, const Rect& newBounds) override; + + std::vector query(const Rect& area) const override; + std::vector query(const Vec2& point) const override; + std::vector> queryCollisions() const override; + + void clear() override; + size_t size() const override; + bool empty() const override; + + void rebuild() override; + + void setCellSize(float cellSize); + float getCellSize() const { return cellSize_; } + +private: + CellKey getCellKey(float x, float y) const; + void getCellsForRect(const Rect& rect, std::vector& cells) const; + void insertIntoCells(Node* node, const Rect& bounds); + void removeFromCells(Node* node, const Rect& bounds); + + float cellSize_; + std::unordered_map, CellKeyHash> grid_; + std::unordered_map objectBounds_; + size_t objectCount_ = 0; +}; + +} diff --git a/Easy2D/include/easy2d/spatial/spatial_index.h b/Easy2D/include/easy2d/spatial/spatial_index.h new file mode 100644 index 0000000..b78e32d --- /dev/null +++ b/Easy2D/include/easy2d/spatial/spatial_index.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include +#include + +namespace easy2d { + +class Node; + +enum class SpatialStrategy { + Auto, + QuadTree, + SpatialHash +}; + +struct SpatialQueryResult { + Node* node; + Rect bounds; +}; + +class ISpatialIndex { +public: + virtual ~ISpatialIndex() = default; + + virtual void insert(Node* node, const Rect& bounds) = 0; + virtual void remove(Node* node) = 0; + virtual void update(Node* node, const Rect& newBounds) = 0; + + virtual std::vector query(const Rect& area) const = 0; + virtual std::vector query(const Vec2& point) const = 0; + virtual std::vector> queryCollisions() const = 0; + + virtual void clear() = 0; + virtual size_t size() const = 0; + virtual bool empty() const = 0; + + virtual void rebuild() = 0; +}; + +using SpatialIndexPtr = std::unique_ptr; + +} diff --git a/Easy2D/include/easy2d/spatial/spatial_manager.h b/Easy2D/include/easy2d/spatial/spatial_manager.h new file mode 100644 index 0000000..95589f3 --- /dev/null +++ b/Easy2D/include/easy2d/spatial/spatial_manager.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include + +namespace easy2d { + +class SpatialManager { +public: + using QueryCallback = std::function; + + SpatialManager(); + explicit SpatialManager(const Rect& worldBounds); + ~SpatialManager() = default; + + void setStrategy(SpatialStrategy strategy); + void setAutoThresholds(size_t quadTreeThreshold, size_t hashThreshold); + + void setWorldBounds(const Rect& bounds); + Rect getWorldBounds() const { return worldBounds_; } + + void insert(Node* node, const Rect& bounds); + void remove(Node* node); + void update(Node* node, const Rect& newBounds); + + std::vector query(const Rect& area) const; + std::vector query(const Vec2& point) const; + std::vector> queryCollisions() const; + + void query(const Rect& area, const QueryCallback& callback) const; + void query(const Vec2& point, const QueryCallback& callback) const; + + void clear(); + size_t size() const; + bool empty() const; + + void rebuild(); + void optimize(); + + SpatialStrategy getCurrentStrategy() const; + const char* getStrategyName() const; + + static std::unique_ptr createIndex(SpatialStrategy strategy, const Rect& bounds); + +private: + void selectOptimalStrategy(); + + SpatialStrategy currentStrategy_ = SpatialStrategy::Auto; + SpatialStrategy activeStrategy_ = SpatialStrategy::QuadTree; + std::unique_ptr index_; + Rect worldBounds_; + + size_t quadTreeThreshold_ = 1000; + size_t hashThreshold_ = 5000; + + mutable size_t queryCount_ = 0; + mutable size_t totalQueryTime_ = 0; +}; + +} diff --git a/Easy2D/include/easy2d/ui/button.h b/Easy2D/include/easy2d/ui/button.h new file mode 100644 index 0000000..b290776 --- /dev/null +++ b/Easy2D/include/easy2d/ui/button.h @@ -0,0 +1,169 @@ +#pragma once + +#include +#include +#include +#include + +namespace easy2d { + +// 图片缩放模式 +enum class ImageScaleMode { + Original, // 使用原图大小 + Stretch, // 拉伸填充 + ScaleFit, // 等比缩放,保持完整显示 + ScaleFill // 等比缩放,填充整个区域(可能裁剪) +}; + +// ============================================================================ +// 基础按钮类 +// ============================================================================ +class Button : public Widget { +public: + Button(); + ~Button() override = default; + + static Ptr