feat: 添加推箱子游戏示例及相关资源文件
添加推箱子游戏示例代码,包括游戏场景、音频控制器、UI按钮等核心组件。新增游戏资源文件如图片、音效、字体等。同时完善动画系统、脚本绑定、渲染后端等基础功能模块,并引入Squirrel脚本支持。 新增功能包括: - 推箱子游戏核心逻辑与场景管理 - 音频播放与控制功能 - 游戏数据存储与读取 - 基础UI组件实现 - Squirrel脚本绑定系统 - OpenGL渲染后端支持 - 动画系统基础框架 资源文件包括: - 游戏角色、箱子、墙壁等素材图片 - 背景音乐和音效文件 - 游戏字体文件 - 游戏配置文件
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(xargs:*)",
|
||||||
|
"Bash(wc -l \"C:\\\\Users\\\\soulcoco\\\\Desktop\\\\Easy2D\\\\Easy2D-dev\\\\Easy2D\\\\src\"/**/*.cpp)",
|
||||||
|
"Bash(wc -l:*)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
#include "audio_context.h"
|
||||||
|
|
||||||
|
namespace pushbox {
|
||||||
|
|
||||||
|
static easy2d::WeakPtr<AudioController> g_audioController;
|
||||||
|
|
||||||
|
void setAudioController(const easy2d::Ptr<AudioController>& controller) {
|
||||||
|
g_audioController = controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
easy2d::Ptr<AudioController> getAudioController() { return g_audioController.lock(); }
|
||||||
|
|
||||||
|
} // namespace pushbox
|
||||||
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/easy2d.h>
|
||||||
|
|
||||||
|
namespace pushbox {
|
||||||
|
|
||||||
|
class AudioController;
|
||||||
|
|
||||||
|
void setAudioController(const easy2d::Ptr<AudioController>& controller);
|
||||||
|
easy2d::Ptr<AudioController> getAudioController();
|
||||||
|
|
||||||
|
} // namespace pushbox
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
@ -0,0 +1,148 @@
|
||||||
|
#include "storage.h"
|
||||||
|
|
||||||
|
#include <easy2d/utils/data.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace pushbox {
|
||||||
|
|
||||||
|
static easy2d::DataStore g_store;
|
||||||
|
static std::filesystem::path g_filePath;
|
||||||
|
static bool g_loaded = false;
|
||||||
|
|
||||||
|
// 默认配置内容
|
||||||
|
static const char* DEFAULT_CONFIG = R"([game]
|
||||||
|
level = 1
|
||||||
|
sound = true
|
||||||
|
|
||||||
|
[best]
|
||||||
|
)";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从 romfs 加载默认配置文件
|
||||||
|
* @return 配置文件内容,如果失败则返回空字符串
|
||||||
|
* @note Switch 平台上 romfs 路径格式为 romfs:/pushbox.ini
|
||||||
|
*/
|
||||||
|
static std::string loadDefaultConfigFromRomfs() {
|
||||||
|
// 尝试多个可能的路径(按优先级排序)
|
||||||
|
const char* paths[] = {
|
||||||
|
"romfs:/pushbox.ini", // Switch romfs 正确路径格式
|
||||||
|
"romfs/pushbox.ini", // 开发环境相对路径
|
||||||
|
"pushbox.ini", // 当前目录
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const char* path : paths) {
|
||||||
|
if (std::filesystem::exists(path)) {
|
||||||
|
std::ifstream file(path, std::ios::binary);
|
||||||
|
if (file) {
|
||||||
|
std::ostringstream buffer;
|
||||||
|
buffer << file.rdbuf();
|
||||||
|
return buffer.str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果找不到文件,返回内置的默认配置
|
||||||
|
return DEFAULT_CONFIG;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将配置字符串写入临时文件并加载
|
||||||
|
* @param content 配置内容
|
||||||
|
* @return 临时文件路径
|
||||||
|
*/
|
||||||
|
static std::filesystem::path writeConfigToTempFile(const std::string& content) {
|
||||||
|
auto tempPath = std::filesystem::temp_directory_path() / "pushbox_default.ini";
|
||||||
|
std::ofstream file(tempPath, std::ios::binary);
|
||||||
|
if (file) {
|
||||||
|
file << content;
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
return tempPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ensureLoaded() {
|
||||||
|
if (g_loaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 首先尝试从可执行目录加载用户配置
|
||||||
|
if (!g_filePath.empty() && std::filesystem::exists(g_filePath)) {
|
||||||
|
g_store.load(g_filePath.string());
|
||||||
|
} else {
|
||||||
|
// 从 romfs 加载默认配置
|
||||||
|
std::string defaultConfig = loadDefaultConfigFromRomfs();
|
||||||
|
if (!defaultConfig.empty()) {
|
||||||
|
auto tempPath = writeConfigToTempFile(defaultConfig);
|
||||||
|
g_store.load(tempPath.string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void initStorage(const std::filesystem::path& baseDir) {
|
||||||
|
g_filePath = baseDir / "pushbox.ini";
|
||||||
|
|
||||||
|
// 首先尝试从可执行目录加载用户配置
|
||||||
|
if (std::filesystem::exists(g_filePath)) {
|
||||||
|
g_store.load(g_filePath.string());
|
||||||
|
} else {
|
||||||
|
// 从 romfs 加载默认配置
|
||||||
|
std::string defaultConfig = loadDefaultConfigFromRomfs();
|
||||||
|
if (!defaultConfig.empty()) {
|
||||||
|
auto tempPath = writeConfigToTempFile(defaultConfig);
|
||||||
|
g_store.load(tempPath.string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int loadCurrentLevel(int defaultValue) {
|
||||||
|
ensureLoaded();
|
||||||
|
int level = g_store.getInt("game", "level", defaultValue);
|
||||||
|
if (level < 1) {
|
||||||
|
level = 1;
|
||||||
|
}
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveCurrentLevel(int level) {
|
||||||
|
ensureLoaded();
|
||||||
|
g_store.setInt("game", "level", level);
|
||||||
|
if (!g_filePath.empty()) {
|
||||||
|
g_store.save(g_filePath.string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool loadSoundOpen(bool defaultValue) {
|
||||||
|
ensureLoaded();
|
||||||
|
return g_store.getBool("game", "sound", defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveSoundOpen(bool open) {
|
||||||
|
ensureLoaded();
|
||||||
|
g_store.setBool("game", "sound", open);
|
||||||
|
if (!g_filePath.empty()) {
|
||||||
|
g_store.save(g_filePath.string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int loadBestStep(int level, int defaultValue) {
|
||||||
|
ensureLoaded();
|
||||||
|
std::string key = "level" + std::to_string(level);
|
||||||
|
return g_store.getInt("best", key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveBestStep(int level, int step) {
|
||||||
|
ensureLoaded();
|
||||||
|
std::string key = "level" + std::to_string(level);
|
||||||
|
g_store.setInt("best", key, step);
|
||||||
|
if (!g_filePath.empty()) {
|
||||||
|
g_store.save(g_filePath.string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path storageFilePath() { return g_filePath; }
|
||||||
|
|
||||||
|
} // namespace pushbox
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
namespace pushbox {
|
||||||
|
|
||||||
|
void initStorage(const std::filesystem::path& baseDir);
|
||||||
|
|
||||||
|
int loadCurrentLevel(int defaultValue = 1);
|
||||||
|
void saveCurrentLevel(int level);
|
||||||
|
|
||||||
|
bool loadSoundOpen(bool defaultValue = true);
|
||||||
|
void saveSoundOpen(bool open);
|
||||||
|
|
||||||
|
int loadBestStep(int level, int defaultValue = 0);
|
||||||
|
void saveBestStep(int level, int step);
|
||||||
|
|
||||||
|
std::filesystem::path storageFilePath();
|
||||||
|
|
||||||
|
} // namespace pushbox
|
||||||
|
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
#include <easy2d/easy2d.h>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
// Pushbox 游戏核心头文件
|
||||||
|
#include "core/data.h"
|
||||||
|
#include "core/storage.h"
|
||||||
|
#include "scenes/start_scene.h"
|
||||||
|
|
||||||
|
// Nintendo Switch 平台支持
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
#include <switch.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Switch 上的存储路径
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
static const char *SWITCH_STORAGE_PATH = "sdmc:/switch/pushbox";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static std::filesystem::path getExecutableDir(int argc, char **argv) {
|
||||||
|
// Nintendo Switch 上使用 SD 卡路径
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
// 创建目录(如果不存在)
|
||||||
|
std::filesystem::create_directories(SWITCH_STORAGE_PATH);
|
||||||
|
return SWITCH_STORAGE_PATH;
|
||||||
|
#else
|
||||||
|
if (argc <= 0 || argv == nullptr || argv[0] == nullptr) {
|
||||||
|
return std::filesystem::current_path();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::error_code ec;
|
||||||
|
auto exePath = std::filesystem::absolute(argv[0], ec);
|
||||||
|
if (ec) {
|
||||||
|
return std::filesystem::current_path();
|
||||||
|
}
|
||||||
|
return exePath.parent_path();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static float parseAutoQuitSeconds(int argc, char **argv) {
|
||||||
|
for (int i = 1; i < argc; i++) {
|
||||||
|
if (!argv[i]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const std::string arg = argv[i];
|
||||||
|
const std::string prefix = "--autoquit=";
|
||||||
|
if (arg.rfind(prefix, 0) == 0) {
|
||||||
|
try {
|
||||||
|
return std::stof(arg.substr(prefix.size()));
|
||||||
|
} catch (...) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
easy2d::Logger::init();
|
||||||
|
easy2d::Logger::setLevel(easy2d::LogLevel::Info);
|
||||||
|
|
||||||
|
auto &app = easy2d::Application::instance();
|
||||||
|
|
||||||
|
easy2d::AppConfig config;
|
||||||
|
config.title = "推箱子";
|
||||||
|
config.width = 640;
|
||||||
|
config.height = 480;
|
||||||
|
config.vsync = true;
|
||||||
|
config.fpsLimit = 0;
|
||||||
|
|
||||||
|
if (!app.init(config)) {
|
||||||
|
easy2d::Logger::shutdown();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto exeDir = getExecutableDir(argc, argv);
|
||||||
|
auto &resources = app.resources();
|
||||||
|
resources.addSearchPath(exeDir.string());
|
||||||
|
resources.addSearchPath((exeDir / "assets").string());
|
||||||
|
resources.addSearchPath((exeDir.parent_path() / "assets").string());
|
||||||
|
resources.addSearchPath((exeDir.parent_path() / "src").string());
|
||||||
|
resources.addSearchPath("assets");
|
||||||
|
resources.addSearchPath("src");
|
||||||
|
|
||||||
|
pushbox::initStorage(exeDir);
|
||||||
|
pushbox::g_CurrentLevel = pushbox::loadCurrentLevel(1);
|
||||||
|
if (pushbox::g_CurrentLevel > MAX_LEVEL) {
|
||||||
|
pushbox::g_CurrentLevel = 1;
|
||||||
|
}
|
||||||
|
pushbox::g_SoundOpen = pushbox::loadSoundOpen(true);
|
||||||
|
|
||||||
|
// 进入开始场景(主界面)
|
||||||
|
app.enterScene(easy2d::makePtr<pushbox::StartScene>());
|
||||||
|
|
||||||
|
const float autoQuitSeconds = parseAutoQuitSeconds(argc, argv);
|
||||||
|
if (autoQuitSeconds > 0.0f) {
|
||||||
|
app.timers().addTimer(autoQuitSeconds, [&app]() { app.quit(); });
|
||||||
|
}
|
||||||
|
app.run();
|
||||||
|
|
||||||
|
app.shutdown();
|
||||||
|
easy2d::Logger::shutdown();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
#include "audio_controller.h"
|
||||||
|
|
||||||
|
#include "../core/storage.h"
|
||||||
|
|
||||||
|
namespace pushbox {
|
||||||
|
|
||||||
|
easy2d::Ptr<AudioController> AudioController::create() { return easy2d::makePtr<AudioController>(); }
|
||||||
|
|
||||||
|
void AudioController::onEnter() {
|
||||||
|
Node::onEnter();
|
||||||
|
|
||||||
|
if (!loaded_) {
|
||||||
|
auto& resources = easy2d::Application::instance().resources();
|
||||||
|
|
||||||
|
background_ = resources.loadSound("pushbox_bg", "assets/audio/background.wav");
|
||||||
|
manMove_ = resources.loadSound("pushbox_manmove", "assets/audio/manmove.wav");
|
||||||
|
boxMove_ = resources.loadSound("pushbox_boxmove", "assets/audio/boxmove.wav");
|
||||||
|
|
||||||
|
if (background_) {
|
||||||
|
background_->setLooping(true);
|
||||||
|
background_->play();
|
||||||
|
}
|
||||||
|
|
||||||
|
loaded_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
setEnabled(g_SoundOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioController::setEnabled(bool enabled) {
|
||||||
|
enabled_ = enabled;
|
||||||
|
g_SoundOpen = enabled;
|
||||||
|
saveSoundOpen(enabled);
|
||||||
|
|
||||||
|
if (!background_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enabled_) {
|
||||||
|
background_->resume();
|
||||||
|
} else {
|
||||||
|
background_->pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioController::playManMove() {
|
||||||
|
if (!enabled_ || !manMove_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
manMove_->play();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AudioController::playBoxMove() {
|
||||||
|
if (!enabled_ || !boxMove_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
boxMove_->play();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pushbox
|
||||||
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../core/data.h"
|
||||||
|
#include <easy2d/easy2d.h>
|
||||||
|
|
||||||
|
namespace pushbox {
|
||||||
|
|
||||||
|
class AudioController : public easy2d::Node {
|
||||||
|
public:
|
||||||
|
static easy2d::Ptr<AudioController> create();
|
||||||
|
|
||||||
|
void onEnter() override;
|
||||||
|
|
||||||
|
void setEnabled(bool enabled);
|
||||||
|
bool isEnabled() const { return enabled_; }
|
||||||
|
|
||||||
|
void playManMove();
|
||||||
|
void playBoxMove();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool loaded_ = false;
|
||||||
|
bool enabled_ = true;
|
||||||
|
|
||||||
|
easy2d::Ptr<easy2d::Sound> background_;
|
||||||
|
easy2d::Ptr<easy2d::Sound> manMove_;
|
||||||
|
easy2d::Ptr<easy2d::Sound> boxMove_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pushbox
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 477 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 658 B |
|
After Width: | Height: | Size: 682 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 641 B |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 191 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 642 B |
|
|
@ -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
|
||||||
|
|
@ -0,0 +1,333 @@
|
||||||
|
#include "play_scene.h"
|
||||||
|
|
||||||
|
#include "../core/audio_context.h"
|
||||||
|
#include "../core/storage.h"
|
||||||
|
#include "../nodes/audio_controller.h"
|
||||||
|
#include "start_scene.h"
|
||||||
|
#include "success_scene.h"
|
||||||
|
#include <easy2d/easy2d.h>
|
||||||
|
|
||||||
|
namespace pushbox {
|
||||||
|
|
||||||
|
static easy2d::Ptr<easy2d::FontAtlas> loadFont(int size) {
|
||||||
|
auto& resources = easy2d::Application::instance().resources();
|
||||||
|
return resources.loadFont("assets/font.ttf", size);
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayScene::PlayScene(int level) {
|
||||||
|
setBackgroundColor(easy2d::Colors::Black);
|
||||||
|
|
||||||
|
// 设置视口大小为窗口尺寸
|
||||||
|
auto& app = easy2d::Application::instance();
|
||||||
|
auto& config = app.getConfig();
|
||||||
|
setViewportSize(static_cast<float>(config.width), static_cast<float>(config.height));
|
||||||
|
|
||||||
|
auto& resources = app.resources();
|
||||||
|
|
||||||
|
E2D_LOG_INFO("PlayScene: Loading textures...");
|
||||||
|
|
||||||
|
texWall_ = resources.loadTexture("assets/images/wall.gif");
|
||||||
|
E2D_LOG_INFO("wall texture: {}", texWall_ ? "OK" : "FAILED");
|
||||||
|
|
||||||
|
texPoint_ = resources.loadTexture("assets/images/point.gif");
|
||||||
|
texFloor_ = resources.loadTexture("assets/images/floor.gif");
|
||||||
|
texBox_ = resources.loadTexture("assets/images/box.gif");
|
||||||
|
texBoxInPoint_ = resources.loadTexture("assets/images/boxinpoint.gif");
|
||||||
|
|
||||||
|
if (!texWall_ || !texFloor_ || !texBox_ || !texBoxInPoint_) {
|
||||||
|
E2D_LOG_ERROR("PlayScene: Failed to load basic textures!");
|
||||||
|
}
|
||||||
|
|
||||||
|
texMan_[1] = resources.loadTexture("assets/images/player/manup.gif");
|
||||||
|
texMan_[2] = resources.loadTexture("assets/images/player/mandown.gif");
|
||||||
|
texMan_[3] = resources.loadTexture("assets/images/player/manleft.gif");
|
||||||
|
texMan_[4] = resources.loadTexture("assets/images/player/manright.gif");
|
||||||
|
|
||||||
|
texManPush_[1] = resources.loadTexture("assets/images/player/manhandup.gif");
|
||||||
|
texManPush_[2] = resources.loadTexture("assets/images/player/manhanddown.gif");
|
||||||
|
texManPush_[3] = resources.loadTexture("assets/images/player/manhandleft.gif");
|
||||||
|
texManPush_[4] = resources.loadTexture("assets/images/player/manhandright.gif");
|
||||||
|
|
||||||
|
font28_ = loadFont(28);
|
||||||
|
font20_ = loadFont(20);
|
||||||
|
|
||||||
|
if (!font28_ || !font20_) {
|
||||||
|
E2D_LOG_ERROR("PlayScene: Failed to load fonts!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (font28_) {
|
||||||
|
levelText_ = easy2d::Text::create("", font28_);
|
||||||
|
levelText_->setPosition(520.0f, 30.0f);
|
||||||
|
levelText_->setTextColor(easy2d::Colors::White);
|
||||||
|
addChild(levelText_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (font20_) {
|
||||||
|
stepText_ = easy2d::Text::create("", font20_);
|
||||||
|
stepText_->setPosition(520.0f, 100.0f);
|
||||||
|
stepText_->setTextColor(easy2d::Colors::White);
|
||||||
|
addChild(stepText_);
|
||||||
|
|
||||||
|
bestText_ = easy2d::Text::create("", font20_);
|
||||||
|
bestText_->setPosition(520.0f, 140.0f);
|
||||||
|
bestText_->setTextColor(easy2d::Colors::White);
|
||||||
|
addChild(bestText_);
|
||||||
|
|
||||||
|
auto exitText = easy2d::Text::create("按ESC返回", font20_);
|
||||||
|
exitText->setPosition(520.0f, 250.0f);
|
||||||
|
exitText->setTextColor(easy2d::Colors::White);
|
||||||
|
addChild(exitText);
|
||||||
|
|
||||||
|
auto restartText = easy2d::Text::create("按回车重开", font20_);
|
||||||
|
restartText->setPosition(520.0f, 290.0f);
|
||||||
|
restartText->setTextColor(easy2d::Colors::White);
|
||||||
|
addChild(restartText);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto soundOn = resources.loadTexture("assets/images/soundon.png");
|
||||||
|
auto soundOff = resources.loadTexture("assets/images/soundoff.png");
|
||||||
|
if (soundOn && soundOff) {
|
||||||
|
soundBtn_ = easy2d::ToggleImageButton::create();
|
||||||
|
soundBtn_->setStateImages(soundOff, soundOn);
|
||||||
|
soundBtn_->setCustomSize(static_cast<float>(soundOn->getWidth()),
|
||||||
|
static_cast<float>(soundOn->getHeight()));
|
||||||
|
soundBtn_->setBorder(easy2d::Colors::Transparent, 0.0f);
|
||||||
|
soundBtn_->setPosition(560.0f, 360.0f);
|
||||||
|
soundBtn_->setOnStateChange([](bool on) {
|
||||||
|
if (auto audio = getAudioController()) {
|
||||||
|
audio->setEnabled(on);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
addChild(soundBtn_);
|
||||||
|
}
|
||||||
|
|
||||||
|
mapLayer_ = easy2d::makePtr<easy2d::Node>();
|
||||||
|
mapLayer_->setAnchor(0.0f, 0.0f);
|
||||||
|
mapLayer_->setPosition(0.0f, 0.0f);
|
||||||
|
addChild(mapLayer_);
|
||||||
|
|
||||||
|
// 创建音频控制器
|
||||||
|
auto audioNode = AudioController::create();
|
||||||
|
audioNode->setName("AudioController");
|
||||||
|
addChild(audioNode);
|
||||||
|
setAudioController(audioNode);
|
||||||
|
|
||||||
|
setLevel(level);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayScene::onEnter() {
|
||||||
|
Scene::onEnter();
|
||||||
|
if (soundBtn_) {
|
||||||
|
soundBtn_->setOn(g_SoundOpen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayScene::onUpdate(float dt) {
|
||||||
|
Scene::onUpdate(dt);
|
||||||
|
|
||||||
|
auto& app = easy2d::Application::instance();
|
||||||
|
auto& input = app.input();
|
||||||
|
|
||||||
|
if (input.isKeyPressed(easy2d::Key::Escape)) {
|
||||||
|
app.scenes().replaceScene(
|
||||||
|
easy2d::makePtr<StartScene>(), easy2d::TransitionType::Fade, 0.2f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.isKeyPressed(easy2d::Key::Enter)) {
|
||||||
|
setLevel(g_CurrentLevel);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.isKeyPressed(easy2d::Key::Up)) {
|
||||||
|
move(0, -1, 1);
|
||||||
|
flush();
|
||||||
|
} else if (input.isKeyPressed(easy2d::Key::Down)) {
|
||||||
|
move(0, 1, 2);
|
||||||
|
flush();
|
||||||
|
} else if (input.isKeyPressed(easy2d::Key::Left)) {
|
||||||
|
move(-1, 0, 3);
|
||||||
|
flush();
|
||||||
|
} else if (input.isKeyPressed(easy2d::Key::Right)) {
|
||||||
|
move(1, 0, 4);
|
||||||
|
flush();
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < map_.width; i++) {
|
||||||
|
for (int j = 0; j < map_.height; j++) {
|
||||||
|
Piece p = map_.value[j][i];
|
||||||
|
if (p.type == TYPE::Box && p.isPoint == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gameOver();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayScene::flush() {
|
||||||
|
mapLayer_->removeAllChildren();
|
||||||
|
|
||||||
|
int tileW = texFloor_ ? texFloor_->getWidth() : 32;
|
||||||
|
int tileH = texFloor_ ? texFloor_->getHeight() : 32;
|
||||||
|
|
||||||
|
float offsetX = static_cast<float>((12 - map_.width) / 2) * tileW;
|
||||||
|
float offsetY = static_cast<float>((12 - map_.height) / 2) * tileH;
|
||||||
|
|
||||||
|
for (int i = 0; i < map_.width; i++) {
|
||||||
|
for (int j = 0; j < map_.height; j++) {
|
||||||
|
Piece piece = map_.value[j][i];
|
||||||
|
|
||||||
|
easy2d::Ptr<easy2d::Texture> tex;
|
||||||
|
|
||||||
|
if (piece.type == TYPE::Wall) {
|
||||||
|
tex = texWall_;
|
||||||
|
} else if (piece.type == TYPE::Ground && piece.isPoint) {
|
||||||
|
tex = texPoint_;
|
||||||
|
} else if (piece.type == TYPE::Ground) {
|
||||||
|
tex = texFloor_;
|
||||||
|
} else if (piece.type == TYPE::Box && piece.isPoint) {
|
||||||
|
tex = texBoxInPoint_;
|
||||||
|
} else if (piece.type == TYPE::Box) {
|
||||||
|
tex = texBox_;
|
||||||
|
} else if (piece.type == TYPE::Man && g_Pushing) {
|
||||||
|
tex = texManPush_[g_Direct];
|
||||||
|
} else if (piece.type == TYPE::Man) {
|
||||||
|
tex = texMan_[g_Direct];
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tex) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto sprite = easy2d::Sprite::create(tex);
|
||||||
|
sprite->setAnchor(0.0f, 0.0f);
|
||||||
|
sprite->setPosition(offsetX + static_cast<float>(i * tileW),
|
||||||
|
offsetY + static_cast<float>(j * tileH));
|
||||||
|
mapLayer_->addChild(sprite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayScene::setLevel(int level) {
|
||||||
|
g_CurrentLevel = level;
|
||||||
|
saveCurrentLevel(g_CurrentLevel);
|
||||||
|
|
||||||
|
if (levelText_) {
|
||||||
|
levelText_->setText("第" + std::to_string(level) + "关");
|
||||||
|
}
|
||||||
|
|
||||||
|
setStep(0);
|
||||||
|
|
||||||
|
int bestStep = loadBestStep(level, 0);
|
||||||
|
if (bestText_) {
|
||||||
|
if (bestStep != 0) {
|
||||||
|
bestText_->setText("最佳" + std::to_string(bestStep) + "步");
|
||||||
|
} else {
|
||||||
|
bestText_->setText("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map_ = g_Maps[level - 1];
|
||||||
|
g_Direct = 2;
|
||||||
|
g_Pushing = false;
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayScene::setStep(int step) {
|
||||||
|
step_ = step;
|
||||||
|
if (stepText_) {
|
||||||
|
stepText_->setText("当前" + std::to_string(step) + "步");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayScene::move(int dx, int dy, int direct) {
|
||||||
|
int targetX = dx + map_.roleX;
|
||||||
|
int targetY = dy + map_.roleY;
|
||||||
|
g_Direct = direct;
|
||||||
|
|
||||||
|
if (targetX < 0 || targetX >= map_.width || targetY < 0 || targetY >= map_.height) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map_.value[targetY][targetX].type == TYPE::Wall) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map_.value[targetY][targetX].type == TYPE::Ground) {
|
||||||
|
g_Pushing = false;
|
||||||
|
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
|
||||||
|
map_.value[targetY][targetX].type = TYPE::Man;
|
||||||
|
if (auto audio = getAudioController()) {
|
||||||
|
audio->playManMove();
|
||||||
|
}
|
||||||
|
} else if (map_.value[targetY][targetX].type == TYPE::Box) {
|
||||||
|
g_Pushing = true;
|
||||||
|
|
||||||
|
int boxX = 0;
|
||||||
|
int boxY = 0;
|
||||||
|
switch (g_Direct) {
|
||||||
|
case 1:
|
||||||
|
boxX = targetX;
|
||||||
|
boxY = targetY - 1;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
boxX = targetX;
|
||||||
|
boxY = targetY + 1;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
boxX = targetX - 1;
|
||||||
|
boxY = targetY;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
boxX = targetX + 1;
|
||||||
|
boxY = targetY;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (boxX < 0 || boxX >= map_.width || boxY < 0 || boxY >= map_.height) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map_.value[boxY][boxX].type == TYPE::Wall || map_.value[boxY][boxX].type == TYPE::Box) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
map_.value[boxY][boxX].type = TYPE::Box;
|
||||||
|
map_.value[targetY][targetX].type = TYPE::Man;
|
||||||
|
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
|
||||||
|
|
||||||
|
if (auto audio = getAudioController()) {
|
||||||
|
audio->playBoxMove();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
map_.roleX = targetX;
|
||||||
|
map_.roleY = targetY;
|
||||||
|
setStep(step_ + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayScene::gameOver() {
|
||||||
|
int bestStep = loadBestStep(g_CurrentLevel, 0);
|
||||||
|
if (bestStep == 0 || step_ < bestStep) {
|
||||||
|
saveBestStep(g_CurrentLevel, step_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_CurrentLevel == MAX_LEVEL) {
|
||||||
|
easy2d::Application::instance().scenes().pushScene(easy2d::makePtr<SuccessScene>(),
|
||||||
|
easy2d::TransitionType::Fade, 0.25f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLevel(g_CurrentLevel + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pushbox
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../core/data.h"
|
||||||
|
#include <easy2d/easy2d.h>
|
||||||
|
|
||||||
|
namespace pushbox {
|
||||||
|
|
||||||
|
class PlayScene : public easy2d::Scene {
|
||||||
|
public:
|
||||||
|
explicit PlayScene(int level);
|
||||||
|
|
||||||
|
void onEnter() override;
|
||||||
|
void onUpdate(float dt) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void flush();
|
||||||
|
void setLevel(int level);
|
||||||
|
void setStep(int step);
|
||||||
|
void move(int dx, int dy, int direct);
|
||||||
|
void gameOver();
|
||||||
|
|
||||||
|
int step_ = 0;
|
||||||
|
Map map_{};
|
||||||
|
|
||||||
|
easy2d::Ptr<easy2d::FontAtlas> font28_;
|
||||||
|
easy2d::Ptr<easy2d::FontAtlas> font20_;
|
||||||
|
|
||||||
|
easy2d::Ptr<easy2d::Text> levelText_;
|
||||||
|
easy2d::Ptr<easy2d::Text> stepText_;
|
||||||
|
easy2d::Ptr<easy2d::Text> bestText_;
|
||||||
|
easy2d::Ptr<easy2d::Node> mapLayer_;
|
||||||
|
|
||||||
|
easy2d::Ptr<easy2d::ToggleImageButton> soundBtn_;
|
||||||
|
|
||||||
|
easy2d::Ptr<easy2d::Texture> texWall_;
|
||||||
|
easy2d::Ptr<easy2d::Texture> texPoint_;
|
||||||
|
easy2d::Ptr<easy2d::Texture> texFloor_;
|
||||||
|
easy2d::Ptr<easy2d::Texture> texBox_;
|
||||||
|
easy2d::Ptr<easy2d::Texture> texBoxInPoint_;
|
||||||
|
|
||||||
|
easy2d::Ptr<easy2d::Texture> texMan_[5];
|
||||||
|
easy2d::Ptr<easy2d::Texture> texManPush_[5];
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pushbox
|
||||||
|
|
@ -0,0 +1,126 @@
|
||||||
|
#include "start_scene.h"
|
||||||
|
|
||||||
|
#include "../core/audio_context.h"
|
||||||
|
#include "../core/data.h"
|
||||||
|
#include "../nodes/audio_controller.h"
|
||||||
|
#include "../scenes/play_scene.h"
|
||||||
|
#include "../ui/menu_button.h"
|
||||||
|
#include <easy2d/easy2d.h>
|
||||||
|
|
||||||
|
namespace pushbox {
|
||||||
|
|
||||||
|
StartScene::StartScene() {
|
||||||
|
// 设置视口大小为窗口尺寸
|
||||||
|
auto& app = easy2d::Application::instance();
|
||||||
|
auto& config = app.getConfig();
|
||||||
|
setViewportSize(static_cast<float>(config.width), static_cast<float>(config.height));
|
||||||
|
}
|
||||||
|
|
||||||
|
static easy2d::Ptr<easy2d::FontAtlas> loadMenuFont() {
|
||||||
|
auto& resources = easy2d::Application::instance().resources();
|
||||||
|
return resources.loadFont("assets/font.ttf", 28);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartScene::onEnter() {
|
||||||
|
Scene::onEnter();
|
||||||
|
|
||||||
|
E2D_LOG_INFO("StartScene::onEnter() - BEGIN");
|
||||||
|
|
||||||
|
auto& app = easy2d::Application::instance();
|
||||||
|
auto& resources = app.resources();
|
||||||
|
// 设置红色背景用于测试渲染
|
||||||
|
setBackgroundColor(easy2d::Color(1.0f, 0.0f, 0.0f, 1.0f));
|
||||||
|
E2D_LOG_INFO("StartScene: Background color set to RED for testing");
|
||||||
|
|
||||||
|
if (getChildren().empty()) {
|
||||||
|
E2D_LOG_INFO("StartScene: Creating audio controller...");
|
||||||
|
auto audioNode = AudioController::create();
|
||||||
|
audioNode->setName("audio_controller");
|
||||||
|
addChild(audioNode);
|
||||||
|
setAudioController(audioNode);
|
||||||
|
E2D_LOG_INFO("StartScene: Audio controller created");
|
||||||
|
|
||||||
|
E2D_LOG_INFO("StartScene: Loading background texture...");
|
||||||
|
auto bgTex = resources.loadTexture("assets/images/start.jpg");
|
||||||
|
if (bgTex) {
|
||||||
|
E2D_LOG_INFO("StartScene: Background texture loaded successfully");
|
||||||
|
auto background = easy2d::Sprite::create(bgTex);
|
||||||
|
background->setAnchor(0.0f, 0.0f);
|
||||||
|
background->setPosition(0.0f, 0.0f);
|
||||||
|
float sx = static_cast<float>(app.getConfig().width) / static_cast<float>(bgTex->getWidth());
|
||||||
|
float sy =
|
||||||
|
static_cast<float>(app.getConfig().height) / static_cast<float>(bgTex->getHeight());
|
||||||
|
background->setScale(sx, sy);
|
||||||
|
addChild(background);
|
||||||
|
E2D_LOG_INFO("StartScene: Background sprite added");
|
||||||
|
} else {
|
||||||
|
E2D_LOG_ERROR("StartScene: Failed to load background texture");
|
||||||
|
}
|
||||||
|
|
||||||
|
E2D_LOG_INFO("StartScene: Loading font...");
|
||||||
|
font_ = loadMenuFont();
|
||||||
|
if (font_) {
|
||||||
|
E2D_LOG_INFO("StartScene: Font loaded successfully, creating menu buttons");
|
||||||
|
// 字体加载成功,创建菜单按钮
|
||||||
|
auto startBtn = MenuButton::create(font_, "新游戏", [this]() { startNewGame(); });
|
||||||
|
startBtn->setPosition(app.getConfig().width / 2.0f, 260.0f);
|
||||||
|
addChild(startBtn);
|
||||||
|
|
||||||
|
resumeBtn_ = MenuButton::create(font_, "继续关卡", [this]() { continueGame(); });
|
||||||
|
resumeBtn_->setPosition(app.getConfig().width / 2.0f, 300.0f);
|
||||||
|
addChild(resumeBtn_);
|
||||||
|
|
||||||
|
auto exitBtn = MenuButton::create(font_, "退出", [this]() { exitGame(); });
|
||||||
|
exitBtn->setPosition(app.getConfig().width / 2.0f, 340.0f);
|
||||||
|
addChild(exitBtn);
|
||||||
|
E2D_LOG_INFO("StartScene: Menu buttons created");
|
||||||
|
} else {
|
||||||
|
E2D_LOG_ERROR("StartScene: Failed to load font, menu buttons will not be displayed");
|
||||||
|
}
|
||||||
|
|
||||||
|
E2D_LOG_INFO("StartScene: Loading sound icons...");
|
||||||
|
auto soundOn = resources.loadTexture("assets/images/soundon.png");
|
||||||
|
auto soundOff = resources.loadTexture("assets/images/soundoff.png");
|
||||||
|
if (soundOn && soundOff) {
|
||||||
|
E2D_LOG_INFO("StartScene: Sound icons loaded successfully");
|
||||||
|
soundBtn_ = easy2d::ToggleImageButton::create();
|
||||||
|
soundBtn_->setStateImages(soundOff, soundOn);
|
||||||
|
soundBtn_->setCustomSize(static_cast<float>(soundOn->getWidth()),
|
||||||
|
static_cast<float>(soundOn->getHeight()));
|
||||||
|
soundBtn_->setBorder(easy2d::Colors::Transparent, 0.0f);
|
||||||
|
soundBtn_->setPosition(50.0f, 50.0f);
|
||||||
|
soundBtn_->setOnStateChange([](bool on) {
|
||||||
|
if (auto audio = getAudioController()) {
|
||||||
|
audio->setEnabled(on);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
addChild(soundBtn_);
|
||||||
|
} else {
|
||||||
|
E2D_LOG_WARN("StartScene: Failed to load sound icons");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resumeBtn_) {
|
||||||
|
resumeBtn_->setEnabled(g_CurrentLevel != 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (soundBtn_) {
|
||||||
|
soundBtn_->setOn(g_SoundOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
E2D_LOG_INFO("StartScene::onEnter() - END");
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartScene::startNewGame() {
|
||||||
|
easy2d::Application::instance().scenes().replaceScene(
|
||||||
|
easy2d::makePtr<PlayScene>(1), easy2d::TransitionType::Fade, 0.25f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartScene::continueGame() {
|
||||||
|
easy2d::Application::instance().scenes().replaceScene(
|
||||||
|
easy2d::makePtr<PlayScene>(g_CurrentLevel), easy2d::TransitionType::Fade, 0.25f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartScene::exitGame() { easy2d::Application::instance().quit(); }
|
||||||
|
|
||||||
|
} // namespace pushbox
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/easy2d.h>
|
||||||
|
|
||||||
|
namespace pushbox {
|
||||||
|
|
||||||
|
class MenuButton;
|
||||||
|
|
||||||
|
class StartScene : public easy2d::Scene {
|
||||||
|
public:
|
||||||
|
StartScene();
|
||||||
|
void onEnter() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void startNewGame();
|
||||||
|
void continueGame();
|
||||||
|
void exitGame();
|
||||||
|
|
||||||
|
easy2d::Ptr<MenuButton> resumeBtn_;
|
||||||
|
easy2d::Ptr<easy2d::ToggleImageButton> soundBtn_;
|
||||||
|
easy2d::Ptr<easy2d::FontAtlas> font_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pushbox
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
#include "success_scene.h"
|
||||||
|
|
||||||
|
#include "../ui/menu_button.h"
|
||||||
|
#include <easy2d/app/application.h>
|
||||||
|
#include <easy2d/resource/resource_manager.h>
|
||||||
|
#include <easy2d/scene/scene_manager.h>
|
||||||
|
#include <easy2d/scene/sprite.h>
|
||||||
|
|
||||||
|
namespace pushbox {
|
||||||
|
|
||||||
|
SuccessScene::SuccessScene() {
|
||||||
|
// 设置视口大小为窗口尺寸
|
||||||
|
auto& app = easy2d::Application::instance();
|
||||||
|
auto& config = app.getConfig();
|
||||||
|
setViewportSize(static_cast<float>(config.width), static_cast<float>(config.height));
|
||||||
|
}
|
||||||
|
|
||||||
|
static easy2d::Ptr<easy2d::FontAtlas> loadMenuFont() {
|
||||||
|
auto& resources = easy2d::Application::instance().resources();
|
||||||
|
return resources.loadFont("assets/font.ttf", 28);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SuccessScene::onEnter() {
|
||||||
|
Scene::onEnter();
|
||||||
|
|
||||||
|
auto& app = easy2d::Application::instance();
|
||||||
|
auto& resources = app.resources();
|
||||||
|
setBackgroundColor(easy2d::Colors::Black);
|
||||||
|
|
||||||
|
if (getChildren().empty()) {
|
||||||
|
auto bgTex = resources.loadTexture("assets/images/success.jpg");
|
||||||
|
if (bgTex) {
|
||||||
|
auto background = easy2d::Sprite::create(bgTex);
|
||||||
|
background->setAnchor(0.0f, 0.0f);
|
||||||
|
background->setPosition(0.0f, 0.0f);
|
||||||
|
float sx = static_cast<float>(app.getConfig().width) / static_cast<float>(bgTex->getWidth());
|
||||||
|
float sy =
|
||||||
|
static_cast<float>(app.getConfig().height) / static_cast<float>(bgTex->getHeight());
|
||||||
|
background->setScale(sx, sy);
|
||||||
|
addChild(background);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto font = loadMenuFont();
|
||||||
|
if (font) {
|
||||||
|
auto backBtn = MenuButton::create(font, "回主菜单", []() {
|
||||||
|
auto& scenes = easy2d::Application::instance().scenes();
|
||||||
|
scenes.popScene(easy2d::TransitionType::Fade, 0.2f);
|
||||||
|
scenes.popScene(easy2d::TransitionType::Fade, 0.2f);
|
||||||
|
});
|
||||||
|
backBtn->setPosition(app.getConfig().width / 2.0f, 350.0f);
|
||||||
|
addChild(backBtn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pushbox
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/easy2d.h>
|
||||||
|
|
||||||
|
namespace pushbox {
|
||||||
|
|
||||||
|
class SuccessScene : public easy2d::Scene {
|
||||||
|
public:
|
||||||
|
SuccessScene();
|
||||||
|
void onEnter() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pushbox
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
#include "menu_button.h"
|
||||||
|
|
||||||
|
#include <easy2d/core/color.h>
|
||||||
|
#include <easy2d/event/event.h>
|
||||||
|
|
||||||
|
namespace pushbox {
|
||||||
|
|
||||||
|
easy2d::Ptr<MenuButton> MenuButton::create(easy2d::Ptr<easy2d::FontAtlas> font,
|
||||||
|
const easy2d::String& text,
|
||||||
|
easy2d::Function<void()> onClick) {
|
||||||
|
auto btn = easy2d::makePtr<MenuButton>();
|
||||||
|
btn->setFont(font);
|
||||||
|
btn->setText(text);
|
||||||
|
btn->setPadding(easy2d::Vec2(0.0f, 0.0f));
|
||||||
|
btn->setBackgroundColor(easy2d::Colors::Transparent, easy2d::Colors::Transparent,
|
||||||
|
easy2d::Colors::Transparent);
|
||||||
|
btn->setBorder(easy2d::Colors::Transparent, 0.0f);
|
||||||
|
btn->setTextColor(easy2d::Colors::Black);
|
||||||
|
|
||||||
|
btn->onClick_ = std::move(onClick);
|
||||||
|
btn->setOnClick([wbtn = easy2d::WeakPtr<MenuButton>(btn)]() {
|
||||||
|
if (auto self = wbtn.lock()) {
|
||||||
|
if (self->enabled_ && self->onClick_) {
|
||||||
|
self->onClick_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
btn->getEventDispatcher().addListener(
|
||||||
|
easy2d::EventType::UIHoverEnter,
|
||||||
|
[wbtn = easy2d::WeakPtr<MenuButton>(btn)](easy2d::Event&) {
|
||||||
|
if (auto self = wbtn.lock()) {
|
||||||
|
if (self->enabled_) {
|
||||||
|
self->setTextColor(easy2d::Colors::Blue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
btn->getEventDispatcher().addListener(
|
||||||
|
easy2d::EventType::UIHoverExit,
|
||||||
|
[wbtn = easy2d::WeakPtr<MenuButton>(btn)](easy2d::Event&) {
|
||||||
|
if (auto self = wbtn.lock()) {
|
||||||
|
if (self->enabled_) {
|
||||||
|
self->setTextColor(easy2d::Colors::Black);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return btn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MenuButton::setEnabled(bool enabled) {
|
||||||
|
enabled_ = enabled;
|
||||||
|
setTextColor(enabled ? easy2d::Colors::Black : easy2d::Colors::LightGray);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace pushbox
|
||||||
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/easy2d.h>
|
||||||
|
|
||||||
|
namespace pushbox {
|
||||||
|
|
||||||
|
class MenuButton : public easy2d::Button {
|
||||||
|
public:
|
||||||
|
static easy2d::Ptr<MenuButton> create(easy2d::Ptr<easy2d::FontAtlas> font,
|
||||||
|
const easy2d::String& text,
|
||||||
|
easy2d::Function<void()> onClick);
|
||||||
|
|
||||||
|
void setEnabled(bool enabled);
|
||||||
|
bool isEnabled() const { return enabled_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool enabled_ = true;
|
||||||
|
easy2d::Function<void()> onClick_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace pushbox
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
#include <easy2d/easy2d.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
// Nintendo Switch 平台支持
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
#include <switch.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using namespace easy2d;
|
||||||
|
|
||||||
|
// 加载系统字体的辅助函数
|
||||||
|
easy2d::Ptr<easy2d::FontAtlas> loadSystemFont(int size) {
|
||||||
|
auto &resources = Application::instance().resources();
|
||||||
|
easy2d::Ptr<easy2d::FontAtlas> font = nullptr;
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
// Nintendo Switch 系统字体路径
|
||||||
|
const char *switchFontPaths[] = {
|
||||||
|
"romfs:/font.TTF", // RomFS 中的字体(注意大小写)
|
||||||
|
"romfs:/font.ttf", // 小写备选
|
||||||
|
"sdmc:/switch/pushbox/font.ttf", // SD 卡字体
|
||||||
|
"/switch/pushbox/font.ttf", // 绝对路径
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto *path : switchFontPaths) {
|
||||||
|
font = resources.loadFont(path, size);
|
||||||
|
if (font) {
|
||||||
|
E2D_LOG_INFO("Loaded Switch font: %s", path);
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// Windows 系统字体
|
||||||
|
const char *winFontPaths[] = {
|
||||||
|
"C:/Windows/Fonts/arial.ttf",
|
||||||
|
"C:/Windows/Fonts/segoeui.ttf",
|
||||||
|
"C:/Windows/Fonts/simsun.ttc",
|
||||||
|
"C:/Windows/Fonts/simhei.ttf",
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto *path : winFontPaths) {
|
||||||
|
font = resources.loadFont(path, size);
|
||||||
|
if (font) {
|
||||||
|
E2D_LOG_INFO("Loaded Windows font: %s", path);
|
||||||
|
return font;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
E2D_LOG_WARN("Failed to load any system font!");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SimpleScene : public Scene {
|
||||||
|
public:
|
||||||
|
SimpleScene() {
|
||||||
|
// 设置背景颜色为深蓝色 (使用 RGB 值)
|
||||||
|
setBackgroundColor(Color(0.0f, 0.0f, 0.5f, 1.0f));
|
||||||
|
|
||||||
|
// 创建一个红色填充矩形(用于测试渲染)
|
||||||
|
// 矩形在屏幕左上角,大小 200x200
|
||||||
|
Rect rectBounds(50, 50, 200, 200); // x, y, width, height
|
||||||
|
auto rect = ShapeNode::createFilledRect(rectBounds, Colors::Red);
|
||||||
|
addChild(rect);
|
||||||
|
|
||||||
|
// 创建一个黄色圆形
|
||||||
|
auto circle = ShapeNode::createFilledCircle(Vec2(400, 300), 100, Colors::Yellow);
|
||||||
|
addChild(circle);
|
||||||
|
|
||||||
|
// 创建一个绿色三角形
|
||||||
|
auto triangle = ShapeNode::createFilledTriangle(
|
||||||
|
Vec2(700, 200), Vec2(600, 400), Vec2(800, 400), Colors::Green);
|
||||||
|
addChild(triangle);
|
||||||
|
|
||||||
|
// 创建一个简单的标签
|
||||||
|
auto label = Text::create("Hello Switch!");
|
||||||
|
|
||||||
|
// 加载系统字体
|
||||||
|
auto font = loadSystemFont(48);
|
||||||
|
if (font) {
|
||||||
|
label->setFont(font);
|
||||||
|
E2D_LOG_INFO("Font loaded successfully!");
|
||||||
|
} else {
|
||||||
|
E2D_LOG_WARN("Font loading failed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
label->setTextColor(Colors::White);
|
||||||
|
label->setPosition(640, 100); // 屏幕上方居中
|
||||||
|
label->setAnchor(0.5f, 0.5f);
|
||||||
|
addChild(label);
|
||||||
|
|
||||||
|
E2D_LOG_INFO("SimpleScene created successfully!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
// Nintendo Switch 初始化
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
Result rc;
|
||||||
|
|
||||||
|
// 初始化 nxlink 调试输出(可选)
|
||||||
|
rc = socketInitializeDefault();
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
std::cout << "socketInitializeDefault failed" << std::endl;
|
||||||
|
} else {
|
||||||
|
nxlinkStdio();
|
||||||
|
std::cout << "nxlink initialized!" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化 RomFS(可选)
|
||||||
|
rc = romfsInit();
|
||||||
|
if (R_FAILED(rc)) {
|
||||||
|
std::cout << "romfsInit failed" << std::endl;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::cout << "Starting Easy2D Simple Test..." << std::endl;
|
||||||
|
|
||||||
|
// 配置应用
|
||||||
|
AppConfig config;
|
||||||
|
config.title = "Switch Simple Test";
|
||||||
|
config.width = 1280;
|
||||||
|
config.height = 720;
|
||||||
|
|
||||||
|
// 初始化 Easy2D
|
||||||
|
if (!Application::instance().init(config)) {
|
||||||
|
std::cerr << "Failed to initialize Easy2D!" << std::endl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Easy2D initialized successfully!" << std::endl;
|
||||||
|
|
||||||
|
// 创建场景并设置到场景管理器
|
||||||
|
auto scene = std::make_shared<SimpleScene>();
|
||||||
|
Application::instance().scenes().pushScene(scene);
|
||||||
|
|
||||||
|
std::cout << "Scene started!" << std::endl;
|
||||||
|
|
||||||
|
// 运行主循环
|
||||||
|
Application::instance().run();
|
||||||
|
|
||||||
|
// 清理
|
||||||
|
Application::instance().shutdown();
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
romfsExit();
|
||||||
|
socketExit();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
class Node;
|
||||||
|
|
||||||
|
enum class ActionState
|
||||||
|
{
|
||||||
|
Idle,
|
||||||
|
Running,
|
||||||
|
Paused,
|
||||||
|
Completed
|
||||||
|
};
|
||||||
|
|
||||||
|
class Action
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using ProgressCallback = std::function<void(float)>;
|
||||||
|
using CompletionCallback = std::function<void()>;
|
||||||
|
|
||||||
|
Action();
|
||||||
|
virtual ~Action() = default;
|
||||||
|
|
||||||
|
Action(const Action&) = delete;
|
||||||
|
Action& operator=(const Action&) = delete;
|
||||||
|
Action(Action&&) = default;
|
||||||
|
Action& operator=(Action&&) = default;
|
||||||
|
|
||||||
|
virtual void start(Node* target);
|
||||||
|
virtual void stop();
|
||||||
|
virtual void update(float dt);
|
||||||
|
virtual void step(float dt);
|
||||||
|
|
||||||
|
virtual bool isDone() const = 0;
|
||||||
|
virtual Action* clone() const = 0;
|
||||||
|
virtual Action* reverse() const = 0;
|
||||||
|
|
||||||
|
void pause();
|
||||||
|
void resume();
|
||||||
|
void restart();
|
||||||
|
|
||||||
|
ActionState getState() const { return state_; }
|
||||||
|
float getElapsed() const { return elapsed_; }
|
||||||
|
float getDuration() const { return duration_; }
|
||||||
|
Node* getTarget() const { return target_; }
|
||||||
|
Node* getOriginalTarget() const { return originalTarget_; }
|
||||||
|
|
||||||
|
void setDuration(float duration) { duration_ = duration; }
|
||||||
|
void setSpeed(float speed) { speed_ = speed; }
|
||||||
|
float getSpeed() const { return speed_; }
|
||||||
|
|
||||||
|
void setProgressCallback(ProgressCallback callback) { progressCallback_ = std::move(callback); }
|
||||||
|
void setCompletionCallback(CompletionCallback callback) { completionCallback_ = std::move(callback); }
|
||||||
|
|
||||||
|
void setTag(int tag) { tag_ = tag; }
|
||||||
|
int getTag() const { return tag_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void onStart() {}
|
||||||
|
virtual void onUpdate(float progress) = 0;
|
||||||
|
virtual void onComplete() {}
|
||||||
|
|
||||||
|
void setDone() { state_ = ActionState::Completed; }
|
||||||
|
|
||||||
|
Node* target_ = nullptr;
|
||||||
|
Node* originalTarget_ = nullptr;
|
||||||
|
ActionState state_ = ActionState::Idle;
|
||||||
|
float elapsed_ = 0.0f;
|
||||||
|
float duration_ = 0.0f;
|
||||||
|
float speed_ = 1.0f;
|
||||||
|
int tag_ = -1;
|
||||||
|
ProgressCallback progressCallback_;
|
||||||
|
CompletionCallback completionCallback_;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ActionPtr = std::unique_ptr<Action>;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,286 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "easy2d/action/action.h"
|
||||||
|
#include "easy2d/core/math_types.h"
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
// Interval Action Base
|
||||||
|
class IntervalAction : public Action
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit IntervalAction(float duration);
|
||||||
|
bool isDone() const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Instant Action Base
|
||||||
|
class InstantAction : public Action
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
InstantAction();
|
||||||
|
bool isDone() const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Move Actions
|
||||||
|
class MoveBy : public IntervalAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MoveBy(float duration, const Vec2& delta);
|
||||||
|
|
||||||
|
Action* clone() const override;
|
||||||
|
Action* reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onStart() override;
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Vec2 delta_;
|
||||||
|
Vec2 startPosition_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MoveTo : public IntervalAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MoveTo(float duration, const Vec2& position);
|
||||||
|
|
||||||
|
Action* clone() const override;
|
||||||
|
Action* reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onStart() override;
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Vec2 endPosition_;
|
||||||
|
Vec2 startPosition_;
|
||||||
|
Vec2 delta_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Scale Actions
|
||||||
|
class ScaleBy : public IntervalAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ScaleBy(float duration, float scale);
|
||||||
|
ScaleBy(float duration, float scaleX, float scaleY);
|
||||||
|
ScaleBy(float duration, const Vec2& scale);
|
||||||
|
|
||||||
|
Action* clone() const override;
|
||||||
|
Action* reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onStart() override;
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Vec2 deltaScale_;
|
||||||
|
Vec2 startScale_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ScaleTo : public IntervalAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ScaleTo(float duration, float scale);
|
||||||
|
ScaleTo(float duration, float scaleX, float scaleY);
|
||||||
|
ScaleTo(float duration, const Vec2& scale);
|
||||||
|
|
||||||
|
Action* clone() const override;
|
||||||
|
Action* reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onStart() override;
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Vec2 endScale_;
|
||||||
|
Vec2 startScale_;
|
||||||
|
Vec2 delta_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Rotate Actions
|
||||||
|
class RotateBy : public IntervalAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RotateBy(float duration, float deltaAngle);
|
||||||
|
RotateBy(float duration, float deltaAngleX, float deltaAngleY);
|
||||||
|
|
||||||
|
Action* clone() const override;
|
||||||
|
Action* reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onStart() override;
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
float deltaAngle_ = 0.0f;
|
||||||
|
float startAngle_ = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RotateTo : public IntervalAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RotateTo(float duration, float angle);
|
||||||
|
RotateTo(float duration, float angleX, float angleY);
|
||||||
|
|
||||||
|
Action* clone() const override;
|
||||||
|
Action* reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onStart() override;
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
float endAngle_ = 0.0f;
|
||||||
|
float startAngle_ = 0.0f;
|
||||||
|
float deltaAngle_ = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fade Actions
|
||||||
|
class FadeIn : public IntervalAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit FadeIn(float duration);
|
||||||
|
|
||||||
|
Action* clone() const override;
|
||||||
|
Action* reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onStart() override;
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
float startOpacity_ = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FadeOut : public IntervalAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit FadeOut(float duration);
|
||||||
|
|
||||||
|
Action* clone() const override;
|
||||||
|
Action* reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onStart() override;
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
float startOpacity_ = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FadeTo : public IntervalAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FadeTo(float duration, float opacity);
|
||||||
|
|
||||||
|
Action* clone() const override;
|
||||||
|
Action* reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onStart() override;
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
float endOpacity_ = 0.0f;
|
||||||
|
float startOpacity_ = 0.0f;
|
||||||
|
float deltaOpacity_ = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Composite Actions
|
||||||
|
class Sequence : public IntervalAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Sequence(const std::vector<Action*>& actions);
|
||||||
|
~Sequence();
|
||||||
|
|
||||||
|
Action* clone() const override;
|
||||||
|
Action* reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onStart() override;
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Action*> actions_;
|
||||||
|
int currentIndex_ = 0;
|
||||||
|
float split_ = 0.0f;
|
||||||
|
float last_ = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Spawn : public IntervalAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Spawn(const std::vector<Action*>& actions);
|
||||||
|
~Spawn();
|
||||||
|
|
||||||
|
Action* clone() const override;
|
||||||
|
Action* reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onStart() override;
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Action*> actions_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Loop Action
|
||||||
|
class Loop : public Action
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Loop(Action* action, int times = -1);
|
||||||
|
~Loop();
|
||||||
|
|
||||||
|
bool isDone() const override;
|
||||||
|
Action* clone() const override;
|
||||||
|
Action* reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onStart() override;
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Action* action_ = nullptr;
|
||||||
|
int times_ = 1;
|
||||||
|
int currentTimes_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Delay Action
|
||||||
|
class Delay : public IntervalAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit Delay(float duration);
|
||||||
|
|
||||||
|
Action* clone() const override;
|
||||||
|
Action* reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// CallFunc Action
|
||||||
|
class CallFunc : public InstantAction
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using Callback = std::function<void()>;
|
||||||
|
|
||||||
|
explicit CallFunc(Callback callback);
|
||||||
|
|
||||||
|
Action* clone() const override;
|
||||||
|
Action* reverse() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onUpdate(float progress) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Callback callback_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper functions
|
||||||
|
inline Sequence* sequence(const std::vector<Action*>& actions) { return new Sequence(actions); }
|
||||||
|
inline Spawn* spawn(const std::vector<Action*>& actions) { return new Spawn(actions); }
|
||||||
|
inline Loop* loop(Action* action, int times = -1) { return new Loop(action, times); }
|
||||||
|
inline Delay* delay(float duration) { return new Delay(duration); }
|
||||||
|
inline CallFunc* callFunc(CallFunc::Callback callback) { return new CallFunc(std::move(callback)); }
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/core/math_types.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// ALS 图层信息
|
||||||
|
// ============================================================================
|
||||||
|
struct AlsLayerInfo {
|
||||||
|
std::string aniPath; // 子动画的 ANI 文件路径
|
||||||
|
int zOrder = 0; // 层级顺序
|
||||||
|
Vec2 offset; // 层偏移
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// ALS 解析结果
|
||||||
|
// ============================================================================
|
||||||
|
struct AlsParseResult {
|
||||||
|
bool success = false;
|
||||||
|
std::string errorMessage;
|
||||||
|
std::vector<AlsLayerInfo> layers;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// AlsParser - ALS 复合动画文件解析器
|
||||||
|
// 解析 .als 文件获取多层动画的图层信息
|
||||||
|
// ============================================================================
|
||||||
|
class AlsParser {
|
||||||
|
public:
|
||||||
|
AlsParser() = default;
|
||||||
|
|
||||||
|
/// 从文件解析
|
||||||
|
AlsParseResult parse(const std::string& filePath);
|
||||||
|
|
||||||
|
/// 从内存内容解析
|
||||||
|
AlsParseResult parseFromMemory(const std::string& content,
|
||||||
|
const std::string& basePath = "");
|
||||||
|
|
||||||
|
/// 设置基础路径
|
||||||
|
void setBasePath(const std::string& basePath) {
|
||||||
|
basePath_ = basePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string basePath_;
|
||||||
|
|
||||||
|
std::string resolvePath(const std::string& relativePath) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/animation/ani_parser.h>
|
||||||
|
#include <easy2d/animation/animation_cache.h>
|
||||||
|
#include <string>
|
||||||
|
#include <functional>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// DNF ANI 二进制格式中的节点类型枚举
|
||||||
|
// ============================================================================
|
||||||
|
enum class AniNodeType : uint16_t {
|
||||||
|
Loop = 0,
|
||||||
|
Shadow = 1,
|
||||||
|
Coord = 3,
|
||||||
|
ImageRate = 7,
|
||||||
|
ImageRotate = 8,
|
||||||
|
RGBA = 9,
|
||||||
|
Interpolation = 10,
|
||||||
|
GraphicEffect = 11,
|
||||||
|
Delay = 12,
|
||||||
|
DamageType = 13,
|
||||||
|
DamageBox = 14,
|
||||||
|
AttackBox = 15,
|
||||||
|
PlaySound = 16,
|
||||||
|
Preload = 17,
|
||||||
|
Spectrum = 18,
|
||||||
|
SetFlag = 23,
|
||||||
|
FlipType = 24,
|
||||||
|
LoopStart = 25,
|
||||||
|
LoopEnd = 26,
|
||||||
|
Clip = 27,
|
||||||
|
Operation = 28,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// AniBinaryParser - ANI 二进制格式解析器
|
||||||
|
// 参考 DNF-Porting 的 PvfAnimation 实现
|
||||||
|
// ============================================================================
|
||||||
|
class AniBinaryParser {
|
||||||
|
public:
|
||||||
|
AniBinaryParser() = default;
|
||||||
|
|
||||||
|
/// 从二进制数据解析
|
||||||
|
AniParseResult parse(const uint8_t* data, size_t length);
|
||||||
|
|
||||||
|
/// 从文件解析
|
||||||
|
AniParseResult parseFromFile(const std::string& filePath);
|
||||||
|
|
||||||
|
/// 设置路径替换回调
|
||||||
|
void setPathResolver(PathResolveCallback callback) {
|
||||||
|
pathResolver_ = std::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 设置基础路径
|
||||||
|
void setBasePath(const std::string& basePath) {
|
||||||
|
basePath_ = basePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
PathResolveCallback pathResolver_;
|
||||||
|
std::string basePath_;
|
||||||
|
|
||||||
|
std::string resolvePath(const std::string& relativePath) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/animation/animation_clip.h>
|
||||||
|
#include <easy2d/animation/animation_cache.h>
|
||||||
|
#include <string>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// ANI 文件解析结果
|
||||||
|
// ============================================================================
|
||||||
|
struct AniParseResult {
|
||||||
|
bool success = false;
|
||||||
|
std::string errorMessage;
|
||||||
|
Ptr<AnimationClip> clip;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// AniParser - ANI 脚本文件解析器
|
||||||
|
// 将原始 ANI 文件格式解析为 AnimationClip 数据
|
||||||
|
// ============================================================================
|
||||||
|
class AniParser {
|
||||||
|
public:
|
||||||
|
AniParser() = default;
|
||||||
|
|
||||||
|
/// 从文件解析
|
||||||
|
AniParseResult parse(const std::string& filePath);
|
||||||
|
|
||||||
|
/// 从内存内容解析
|
||||||
|
AniParseResult parseFromMemory(const std::string& content,
|
||||||
|
const std::string& basePath = "");
|
||||||
|
|
||||||
|
/// 设置路径替换回调(对应原始 AdditionalOptions)
|
||||||
|
void setPathResolver(PathResolveCallback callback) {
|
||||||
|
pathResolver_ = std::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 设置基础路径(用于解析相对路径)
|
||||||
|
void setBasePath(const std::string& basePath) {
|
||||||
|
basePath_ = basePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
PathResolveCallback pathResolver_;
|
||||||
|
std::string basePath_;
|
||||||
|
|
||||||
|
std::string resolvePath(const std::string& relativePath) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/scene/sprite.h>
|
||||||
|
#include <easy2d/animation/animation_controller.h>
|
||||||
|
#include <easy2d/animation/animation_cache.h>
|
||||||
|
#include <string>
|
||||||
|
#include <array>
|
||||||
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// AnimatedSprite - 动画精灵节点
|
||||||
|
// 将 AnimationController 与 Sprite 渲染桥接,接入场景图
|
||||||
|
// ============================================================================
|
||||||
|
class AnimatedSprite : public Sprite {
|
||||||
|
public:
|
||||||
|
AnimatedSprite();
|
||||||
|
~AnimatedSprite() override = default;
|
||||||
|
|
||||||
|
// ------ 静态工厂 ------
|
||||||
|
static Ptr<AnimatedSprite> create();
|
||||||
|
static Ptr<AnimatedSprite> create(Ptr<AnimationClip> clip);
|
||||||
|
static Ptr<AnimatedSprite> create(const std::string& aniFilePath);
|
||||||
|
|
||||||
|
// ------ 动画绑定 ------
|
||||||
|
void setAnimationClip(Ptr<AnimationClip> clip);
|
||||||
|
void loadAnimation(const std::string& aniFilePath);
|
||||||
|
Ptr<AnimationClip> getAnimationClip() const;
|
||||||
|
|
||||||
|
// ------ 动画字典 ------
|
||||||
|
void addAnimation(const std::string& name, Ptr<AnimationClip> clip);
|
||||||
|
void play(const std::string& name, bool loop = true);
|
||||||
|
bool hasAnimation(const std::string& name) const;
|
||||||
|
Ptr<AnimationClip> getAnimation(const std::string& name) const;
|
||||||
|
const std::string& getCurrentAnimationName() const;
|
||||||
|
|
||||||
|
// ------ 播放控制(委托 controller_)------
|
||||||
|
void play();
|
||||||
|
void pause();
|
||||||
|
void resume();
|
||||||
|
void stop();
|
||||||
|
void reset();
|
||||||
|
bool isPlaying() const;
|
||||||
|
bool isPaused() const;
|
||||||
|
bool isStopped() const;
|
||||||
|
|
||||||
|
// ------ 属性控制 ------
|
||||||
|
void setLooping(bool loop);
|
||||||
|
bool isLooping() const;
|
||||||
|
void setPlaybackSpeed(float speed);
|
||||||
|
float getPlaybackSpeed() const;
|
||||||
|
|
||||||
|
// ------ 帧控制 ------
|
||||||
|
void setFrameIndex(size_t index);
|
||||||
|
size_t getCurrentFrameIndex() const;
|
||||||
|
size_t getTotalFrames() const;
|
||||||
|
void nextFrame();
|
||||||
|
void prevFrame();
|
||||||
|
|
||||||
|
// ------ 帧范围限制 ------
|
||||||
|
/// 设置帧播放范围(用于精灵图动画,限制在指定范围内循环)
|
||||||
|
/// @param start 起始帧索引(包含)
|
||||||
|
/// @param end 结束帧索引(包含),-1表示不限制
|
||||||
|
void setFrameRange(int start, int end = -1);
|
||||||
|
|
||||||
|
/// 获取当前帧范围
|
||||||
|
/// @return pair<起始帧, 结束帧>,结束帧为-1表示不限制
|
||||||
|
std::pair<int, int> getFrameRange() const;
|
||||||
|
|
||||||
|
/// 清除帧范围限制(恢复播放所有帧)
|
||||||
|
void clearFrameRange();
|
||||||
|
|
||||||
|
/// 检查是否设置了帧范围限制
|
||||||
|
bool hasFrameRange() const;
|
||||||
|
|
||||||
|
// ------ 回调 ------
|
||||||
|
void setCompletionCallback(AnimationController::CompletionCallback cb);
|
||||||
|
void setKeyframeCallback(AnimationController::KeyframeCallback cb);
|
||||||
|
void setSoundTriggerCallback(AnimationController::SoundTriggerCallback cb);
|
||||||
|
|
||||||
|
// ------ 碰撞盒访问(当前帧)------
|
||||||
|
const std::vector<std::array<int32_t, 6>>& getCurrentDamageBoxes() const;
|
||||||
|
const std::vector<std::array<int32_t, 6>>& getCurrentAttackBoxes() const;
|
||||||
|
|
||||||
|
// ------ 帧变换控制 ------
|
||||||
|
/// 设置是否由动画帧数据覆盖节点的 position/scale/rotation
|
||||||
|
/// ANI 动画需要开启(默认),精灵图动画应关闭
|
||||||
|
void setApplyFrameTransform(bool apply) { applyFrameTransform_ = apply; }
|
||||||
|
bool isApplyFrameTransform() const { return applyFrameTransform_; }
|
||||||
|
|
||||||
|
// ------ 自动播放 ------
|
||||||
|
void setAutoPlay(bool autoPlay) { autoPlay_ = autoPlay; }
|
||||||
|
bool isAutoPlay() const { return autoPlay_; }
|
||||||
|
|
||||||
|
// ------ 直接控制器访问 ------
|
||||||
|
AnimationController& getController() { return controller_; }
|
||||||
|
const AnimationController& getController() const { return controller_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onUpdate(float dt) override;
|
||||||
|
void onEnter() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
AnimationController controller_;
|
||||||
|
bool autoPlay_ = false;
|
||||||
|
bool applyFrameTransform_ = true;
|
||||||
|
|
||||||
|
// 动画字典
|
||||||
|
std::unordered_map<std::string, Ptr<AnimationClip>> animations_;
|
||||||
|
std::string currentAnimationName_;
|
||||||
|
static const std::string emptyString_;
|
||||||
|
|
||||||
|
// 帧范围限制(用于精灵图动画)
|
||||||
|
int frameRangeStart_ = 0; // 起始帧索引
|
||||||
|
int frameRangeEnd_ = -1; // 结束帧索引,-1表示不限制
|
||||||
|
|
||||||
|
// 空碰撞盒列表(用于无帧时返回引用)
|
||||||
|
static const std::vector<std::array<int32_t, 6>> emptyBoxes_;
|
||||||
|
|
||||||
|
void applyFrame(const AnimationFrame& frame);
|
||||||
|
void onFrameChanged(size_t oldIdx, size_t newIdx, const AnimationFrame& frame);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/animation/animation_clip.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// 路径替换回调(对应原始 AdditionalOptions)
|
||||||
|
using PathResolveCallback = std::function<std::string(const std::string&)>;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// AnimationCache - 动画片段全局缓存(借鉴 Cocos AnimationCache)
|
||||||
|
// 同一 ANI 文件只解析一次,后续直接复用数据
|
||||||
|
// ============================================================================
|
||||||
|
class AnimationCache {
|
||||||
|
public:
|
||||||
|
static AnimationCache& getInstance() {
|
||||||
|
static AnimationCache instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 加载与获取 ------
|
||||||
|
|
||||||
|
/// 从文件加载(自动缓存),已缓存则直接返回
|
||||||
|
/// 注意:实际的 ANI 解析逻辑在 AniParser 中实现
|
||||||
|
/// 此方法在 animation_cache.cpp 中实现,依赖 AniParser
|
||||||
|
Ptr<AnimationClip> loadClip(const std::string& aniFilePath);
|
||||||
|
|
||||||
|
/// 从缓存获取(不触发加载)
|
||||||
|
Ptr<AnimationClip> getClip(const std::string& name) const {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
auto it = clips_.find(name);
|
||||||
|
if (it != clips_.end()) return it->second;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 手动添加到缓存
|
||||||
|
void addClip(Ptr<AnimationClip> clip, const std::string& name) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
clips_[name] = std::move(clip);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 缓存管理 ------
|
||||||
|
|
||||||
|
bool has(const std::string& name) const {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
return clips_.find(name) != clips_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeClip(const std::string& name) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
clips_.erase(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 移除未被外部引用的动画片段
|
||||||
|
void removeUnusedClips() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
for (auto it = clips_.begin(); it != clips_.end(); ) {
|
||||||
|
if (it->second.use_count() == 1) {
|
||||||
|
it = clips_.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
clips_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t count() const {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
return clips_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 路径配置 ------
|
||||||
|
void setPathResolver(PathResolveCallback resolver) {
|
||||||
|
pathResolver_ = std::move(resolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
PathResolveCallback getPathResolver() const {
|
||||||
|
return pathResolver_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
AnimationCache() = default;
|
||||||
|
~AnimationCache() = default;
|
||||||
|
AnimationCache(const AnimationCache&) = delete;
|
||||||
|
AnimationCache& operator=(const AnimationCache&) = delete;
|
||||||
|
|
||||||
|
mutable std::mutex mutex_;
|
||||||
|
std::unordered_map<std::string, Ptr<AnimationClip>> clips_;
|
||||||
|
PathResolveCallback pathResolver_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 便捷宏
|
||||||
|
#define E2D_ANIMATION_CACHE() ::easy2d::AnimationCache::getInstance()
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,195 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/animation/animation_frame.h>
|
||||||
|
#include <easy2d/animation/sprite_frame_cache.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// AnimationClip - 动画片段(纯数据,可复用)
|
||||||
|
// 借鉴 Cocos:一份 AnimationClip 可被多个 AnimationNode 同时使用
|
||||||
|
// ============================================================================
|
||||||
|
class AnimationClip {
|
||||||
|
public:
|
||||||
|
AnimationClip() = default;
|
||||||
|
|
||||||
|
explicit AnimationClip(const std::string& name)
|
||||||
|
: name_(name) {}
|
||||||
|
|
||||||
|
// ------ 帧管理 ------
|
||||||
|
void addFrame(const AnimationFrame& frame) {
|
||||||
|
frames_.push_back(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
void addFrame(AnimationFrame&& frame) {
|
||||||
|
frames_.push_back(std::move(frame));
|
||||||
|
}
|
||||||
|
|
||||||
|
void insertFrame(size_t index, const AnimationFrame& frame) {
|
||||||
|
assert(index <= frames_.size());
|
||||||
|
frames_.insert(frames_.begin() + static_cast<ptrdiff_t>(index), frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeFrame(size_t index) {
|
||||||
|
assert(index < frames_.size());
|
||||||
|
frames_.erase(frames_.begin() + static_cast<ptrdiff_t>(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearFrames() {
|
||||||
|
frames_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
const AnimationFrame& getFrame(size_t index) const {
|
||||||
|
assert(index < frames_.size());
|
||||||
|
return frames_[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimationFrame& getFrame(size_t index) {
|
||||||
|
assert(index < frames_.size());
|
||||||
|
return frames_[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getFrameCount() const { return frames_.size(); }
|
||||||
|
bool empty() const { return frames_.empty(); }
|
||||||
|
|
||||||
|
// ------ 全局属性(对应原始 AnimationFlag)------
|
||||||
|
FramePropertySet& globalProperties() { return globalProperties_; }
|
||||||
|
const FramePropertySet& globalProperties() const { return globalProperties_; }
|
||||||
|
|
||||||
|
bool isLooping() const {
|
||||||
|
return globalProperties_.getOr<bool>(FramePropertyKey::Loop, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLooping(bool loop) {
|
||||||
|
globalProperties_.withLoop(loop);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 时间信息 ------
|
||||||
|
float getTotalDuration() const {
|
||||||
|
float total = 0.0f;
|
||||||
|
for (const auto& frame : frames_) {
|
||||||
|
total += frame.delay;
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 预计算最大帧尺寸 ------
|
||||||
|
Size getMaxFrameSize() const {
|
||||||
|
Size maxSize;
|
||||||
|
for (const auto& frame : frames_) {
|
||||||
|
if (frame.spriteFrame && frame.spriteFrame->isValid()) {
|
||||||
|
const auto& rect = frame.spriteFrame->getRect();
|
||||||
|
if (rect.size.width > maxSize.width)
|
||||||
|
maxSize.width = rect.size.width;
|
||||||
|
if (rect.size.height > maxSize.height)
|
||||||
|
maxSize.height = rect.size.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 元数据 ------
|
||||||
|
void setName(const std::string& name) { name_ = name; }
|
||||||
|
const std::string& getName() const { return name_; }
|
||||||
|
|
||||||
|
void setSourcePath(const std::string& path) { sourcePath_ = path; }
|
||||||
|
const std::string& getSourcePath() const { return sourcePath_; }
|
||||||
|
|
||||||
|
// ------ 静态工厂 ------
|
||||||
|
static Ptr<AnimationClip> create(const std::string& name = "") {
|
||||||
|
return makePtr<AnimationClip>(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 从精灵图网格创建(所有帧按顺序)
|
||||||
|
static Ptr<AnimationClip> createFromGrid(
|
||||||
|
Ptr<Texture> texture, int frameWidth, int frameHeight,
|
||||||
|
float frameDurationMs = 100.0f, int frameCount = -1,
|
||||||
|
int spacing = 0, int margin = 0)
|
||||||
|
{
|
||||||
|
if (!texture) return nullptr;
|
||||||
|
|
||||||
|
int texW = texture->getWidth();
|
||||||
|
int texH = texture->getHeight();
|
||||||
|
int usableW = texW - 2 * margin;
|
||||||
|
int usableH = texH - 2 * margin;
|
||||||
|
int cols = (usableW + spacing) / (frameWidth + spacing);
|
||||||
|
int rows = (usableH + spacing) / (frameHeight + spacing);
|
||||||
|
int total = (frameCount > 0) ? frameCount : cols * rows;
|
||||||
|
|
||||||
|
auto clip = makePtr<AnimationClip>();
|
||||||
|
for (int i = 0; i < total; ++i) {
|
||||||
|
int col = i % cols;
|
||||||
|
int row = i / cols;
|
||||||
|
if (row >= rows) break;
|
||||||
|
|
||||||
|
// 翻转行顺序:精灵图第0行在顶部,但OpenGL纹理V坐标从底部开始
|
||||||
|
// 所以将行索引翻转,使第0行对应纹理底部(V=1.0),第3行对应纹理顶部(V=0.0)
|
||||||
|
int flippedRow = (rows - 1) - row;
|
||||||
|
|
||||||
|
Rect rect(
|
||||||
|
static_cast<float>(margin + col * (frameWidth + spacing)),
|
||||||
|
static_cast<float>(margin + flippedRow * (frameHeight + spacing)),
|
||||||
|
static_cast<float>(frameWidth),
|
||||||
|
static_cast<float>(frameHeight)
|
||||||
|
);
|
||||||
|
|
||||||
|
auto sf = SpriteFrame::create(texture, rect);
|
||||||
|
AnimationFrame frame;
|
||||||
|
frame.spriteFrame = std::move(sf);
|
||||||
|
frame.delay = frameDurationMs;
|
||||||
|
clip->addFrame(std::move(frame));
|
||||||
|
}
|
||||||
|
return clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 从精灵图网格创建(指定帧索引列表)
|
||||||
|
static Ptr<AnimationClip> createFromGridIndices(
|
||||||
|
Ptr<Texture> texture, int frameWidth, int frameHeight,
|
||||||
|
const std::vector<int>& frameIndices,
|
||||||
|
float frameDurationMs = 100.0f,
|
||||||
|
int spacing = 0, int margin = 0)
|
||||||
|
{
|
||||||
|
if (!texture) return nullptr;
|
||||||
|
|
||||||
|
int texW = texture->getWidth();
|
||||||
|
int texH = texture->getHeight();
|
||||||
|
int usableW = texW - 2 * margin;
|
||||||
|
int usableH = texH - 2 * margin;
|
||||||
|
int cols = (usableW + spacing) / (frameWidth + spacing);
|
||||||
|
int rows = (usableH + spacing) / (frameHeight + spacing);
|
||||||
|
|
||||||
|
auto clip = makePtr<AnimationClip>();
|
||||||
|
for (int idx : frameIndices) {
|
||||||
|
int col = idx % cols;
|
||||||
|
int row = idx / cols;
|
||||||
|
|
||||||
|
// 翻转行顺序:精灵图第0行在顶部,但OpenGL纹理V坐标从底部开始
|
||||||
|
int flippedRow = (rows - 1) - row;
|
||||||
|
|
||||||
|
Rect rect(
|
||||||
|
static_cast<float>(margin + col * (frameWidth + spacing)),
|
||||||
|
static_cast<float>(margin + flippedRow * (frameHeight + spacing)),
|
||||||
|
static_cast<float>(frameWidth),
|
||||||
|
static_cast<float>(frameHeight)
|
||||||
|
);
|
||||||
|
|
||||||
|
auto sf = SpriteFrame::create(texture, rect);
|
||||||
|
AnimationFrame frame;
|
||||||
|
frame.spriteFrame = std::move(sf);
|
||||||
|
frame.delay = frameDurationMs;
|
||||||
|
clip->addFrame(std::move(frame));
|
||||||
|
}
|
||||||
|
return clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string name_;
|
||||||
|
std::string sourcePath_;
|
||||||
|
std::vector<AnimationFrame> frames_;
|
||||||
|
FramePropertySet globalProperties_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/animation/animation_clip.h>
|
||||||
|
#include <easy2d/animation/interpolation_engine.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 动画播放状态
|
||||||
|
// ============================================================================
|
||||||
|
enum class AnimPlayState : uint8 {
|
||||||
|
Stopped,
|
||||||
|
Playing,
|
||||||
|
Paused
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// AnimationController - 动画播放控制器
|
||||||
|
// 借鉴 Cocos Creator 的 AnimationState:纯播放逻辑,不持有渲染资源
|
||||||
|
// ============================================================================
|
||||||
|
class AnimationController {
|
||||||
|
public:
|
||||||
|
// 回调类型定义
|
||||||
|
using FrameChangeCallback = std::function<void(size_t oldIdx, size_t newIdx,
|
||||||
|
const AnimationFrame& frame)>;
|
||||||
|
using KeyframeCallback = std::function<void(int flagIndex)>;
|
||||||
|
using SoundTriggerCallback = std::function<void(const std::string& path)>;
|
||||||
|
using CompletionCallback = std::function<void()>;
|
||||||
|
|
||||||
|
AnimationController() = default;
|
||||||
|
|
||||||
|
// ------ 绑定动画数据 ------
|
||||||
|
void setClip(Ptr<AnimationClip> clip);
|
||||||
|
Ptr<AnimationClip> getClip() const { return clip_; }
|
||||||
|
|
||||||
|
// ------ 播放控制 ------
|
||||||
|
void play();
|
||||||
|
void pause();
|
||||||
|
void resume();
|
||||||
|
void stop();
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
// ------ 帧控制 ------
|
||||||
|
void setFrameIndex(size_t index);
|
||||||
|
void nextFrame();
|
||||||
|
void prevFrame();
|
||||||
|
|
||||||
|
// ------ 核心更新(每帧调用)------
|
||||||
|
void update(float dt);
|
||||||
|
|
||||||
|
// ------ 状态查询 ------
|
||||||
|
AnimPlayState getState() const { return state_; }
|
||||||
|
bool isPlaying() const { return state_ == AnimPlayState::Playing; }
|
||||||
|
bool isPaused() const { return state_ == AnimPlayState::Paused; }
|
||||||
|
bool isStopped() const { return state_ == AnimPlayState::Stopped; }
|
||||||
|
|
||||||
|
size_t getCurrentFrameIndex() const { return currentFrameIndex_; }
|
||||||
|
size_t getTotalFrames() const;
|
||||||
|
const AnimationFrame& getCurrentFrame() const;
|
||||||
|
|
||||||
|
float getPlaybackSpeed() const { return playbackSpeed_; }
|
||||||
|
void setPlaybackSpeed(float speed) { playbackSpeed_ = speed; }
|
||||||
|
|
||||||
|
bool isLooping() const;
|
||||||
|
void setLooping(bool loop);
|
||||||
|
|
||||||
|
// ------ 插值状态 ------
|
||||||
|
float getInterpolationFactor() const { return interpolationFactor_; }
|
||||||
|
bool isInterpolating() const { return interpolating_; }
|
||||||
|
|
||||||
|
// ------ 回调注册 ------
|
||||||
|
void setFrameChangeCallback(FrameChangeCallback cb) { onFrameChange_ = std::move(cb); }
|
||||||
|
void setKeyframeCallback(KeyframeCallback cb) { onKeyframe_ = std::move(cb); }
|
||||||
|
void setSoundTriggerCallback(SoundTriggerCallback cb) { onSoundTrigger_ = std::move(cb); }
|
||||||
|
void setCompletionCallback(CompletionCallback cb) { onComplete_ = std::move(cb); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ptr<AnimationClip> clip_;
|
||||||
|
AnimPlayState state_ = AnimPlayState::Stopped;
|
||||||
|
|
||||||
|
size_t currentFrameIndex_ = 0;
|
||||||
|
float accumulatedTime_ = 0.0f; // 当前帧已累积时间 (ms)
|
||||||
|
float playbackSpeed_ = 1.0f;
|
||||||
|
bool loopOverride_ = false; // 外部循环覆盖值
|
||||||
|
bool hasLoopOverride_ = false; // 是否使用外部循环覆盖
|
||||||
|
|
||||||
|
// 插值状态
|
||||||
|
bool interpolating_ = false;
|
||||||
|
float interpolationFactor_ = 0.0f;
|
||||||
|
|
||||||
|
// 回调
|
||||||
|
FrameChangeCallback onFrameChange_;
|
||||||
|
KeyframeCallback onKeyframe_;
|
||||||
|
SoundTriggerCallback onSoundTrigger_;
|
||||||
|
CompletionCallback onComplete_;
|
||||||
|
|
||||||
|
// 内部方法
|
||||||
|
void advanceFrame(size_t newIndex);
|
||||||
|
void processFrameProperties(const AnimationFrame& frame);
|
||||||
|
void updateInterpolation();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
class Node;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 动画事件类型
|
||||||
|
// ============================================================================
|
||||||
|
enum class AnimationEventType : uint32_t {
|
||||||
|
FrameChanged = 0x2001, // 帧切换
|
||||||
|
KeyframeHit = 0x2002, // 关键帧触发
|
||||||
|
SoundTrigger = 0x2003, // 音效触发
|
||||||
|
AnimationStart = 0x2004, // 动画开始播放
|
||||||
|
AnimationEnd = 0x2005, // 动画播放结束
|
||||||
|
AnimationLoop = 0x2006, // 动画循环一轮
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 动画事件数据
|
||||||
|
// ============================================================================
|
||||||
|
struct AnimationEvent {
|
||||||
|
AnimationEventType type;
|
||||||
|
size_t frameIndex = 0;
|
||||||
|
size_t previousFrameIndex = 0;
|
||||||
|
int keyframeFlag = -1;
|
||||||
|
std::string soundPath;
|
||||||
|
Node* source = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 动画事件回调类型
|
||||||
|
// ============================================================================
|
||||||
|
using AnimationEventCallback = std::function<void(const AnimationEvent&)>;
|
||||||
|
using KeyframeHitCallback = std::function<void(int flagIndex)>;
|
||||||
|
using AnimationCompleteCallback = std::function<void()>;
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/animation/sprite_frame.h>
|
||||||
|
#include <easy2d/animation/frame_property.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// AnimationFrame - 单帧数据
|
||||||
|
// 引用 SpriteFrame 而非直接持有纹理(借鉴 Cocos 模式)
|
||||||
|
// 通过 FramePropertySet 支持不固定数据(ANI Flag 系统增强版)
|
||||||
|
// ============================================================================
|
||||||
|
struct AnimationFrame {
|
||||||
|
// ------ 核心数据(固定部分)------
|
||||||
|
Ptr<SpriteFrame> spriteFrame; // 精灵帧引用(Cocos 模式)
|
||||||
|
std::string texturePath; // 原始图片路径(用于解析时定位资源)
|
||||||
|
int textureIndex = 0; // 精灵图集索引
|
||||||
|
Vec2 offset; // 位置偏移
|
||||||
|
float delay = 100.0f; // 帧延迟(毫秒)
|
||||||
|
|
||||||
|
// ------ 碰撞盒数据(DNF ANI 格式)------
|
||||||
|
std::vector<std::array<int32_t, 6>> damageBoxes; // 伤害碰撞盒
|
||||||
|
std::vector<std::array<int32_t, 6>> attackBoxes; // 攻击碰撞盒
|
||||||
|
|
||||||
|
// ------ 不固定数据(属性集合)------
|
||||||
|
FramePropertySet properties; // 类型安全的 Flag 系统
|
||||||
|
|
||||||
|
// ------ 便捷方法 ------
|
||||||
|
bool hasTexture() const {
|
||||||
|
return spriteFrame != nullptr && spriteFrame->isValid();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasInterpolation() const {
|
||||||
|
return properties.getOr<bool>(FramePropertyKey::Interpolation, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasKeyframeCallback() const {
|
||||||
|
return properties.has(FramePropertyKey::SetFlag);
|
||||||
|
}
|
||||||
|
|
||||||
|
int getKeyframeIndex() const {
|
||||||
|
return properties.getOr<int>(FramePropertyKey::SetFlag, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 getEffectiveScale() const {
|
||||||
|
return properties.getOr<Vec2>(FramePropertyKey::ImageRate, Vec2::One());
|
||||||
|
}
|
||||||
|
|
||||||
|
float getEffectiveRotation() const {
|
||||||
|
return properties.getOr<float>(FramePropertyKey::ImageRotate, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getEffectiveColor() const {
|
||||||
|
return properties.getOr<Color>(FramePropertyKey::ColorTint, Colors::White);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/scene/node.h>
|
||||||
|
#include <easy2d/animation/animation_clip.h>
|
||||||
|
#include <easy2d/animation/animation_controller.h>
|
||||||
|
#include <easy2d/animation/animation_cache.h>
|
||||||
|
#include <easy2d/animation/frame_renderer.h>
|
||||||
|
#include <easy2d/animation/animation_event.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// AnimationNode - 动画节点(继承 Node)
|
||||||
|
// 使用 FrameRenderer 单渲染器策略,不依赖 Sprite 基类
|
||||||
|
// 适用于需要独立渲染控制的动画(如特效、复合动画图层)
|
||||||
|
// ============================================================================
|
||||||
|
class AnimationNode : public Node {
|
||||||
|
public:
|
||||||
|
AnimationNode();
|
||||||
|
~AnimationNode() override = default;
|
||||||
|
|
||||||
|
// ------ 静态工厂(Cocos 风格)------
|
||||||
|
static Ptr<AnimationNode> create();
|
||||||
|
static Ptr<AnimationNode> create(Ptr<AnimationClip> clip);
|
||||||
|
static Ptr<AnimationNode> create(const std::string& aniFilePath);
|
||||||
|
|
||||||
|
// ------ 动画数据 ------
|
||||||
|
void setClip(Ptr<AnimationClip> clip);
|
||||||
|
Ptr<AnimationClip> getClip() const;
|
||||||
|
bool loadFromFile(const std::string& aniFilePath);
|
||||||
|
|
||||||
|
// ------ 播放控制 ------
|
||||||
|
void play();
|
||||||
|
void pause();
|
||||||
|
void resume();
|
||||||
|
void stop();
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
bool isPlaying() const;
|
||||||
|
bool isPaused() const;
|
||||||
|
bool isStopped() const;
|
||||||
|
|
||||||
|
void setPlaybackSpeed(float speed);
|
||||||
|
float getPlaybackSpeed() const;
|
||||||
|
void setLooping(bool loop);
|
||||||
|
bool isLooping() const;
|
||||||
|
|
||||||
|
// ------ 帧控制 ------
|
||||||
|
void setFrameIndex(size_t index);
|
||||||
|
size_t getCurrentFrameIndex() const;
|
||||||
|
size_t getTotalFrames() const;
|
||||||
|
|
||||||
|
// ------ 事件回调 ------
|
||||||
|
void setKeyframeCallback(KeyframeHitCallback callback);
|
||||||
|
void setCompletionCallback(AnimationCompleteCallback callback);
|
||||||
|
void setFrameChangeCallback(AnimationController::FrameChangeCallback callback);
|
||||||
|
void addEventListener(AnimationEventCallback callback);
|
||||||
|
|
||||||
|
// ------ 视觉属性 ------
|
||||||
|
void setTintColor(const Color& color);
|
||||||
|
Color getTintColor() const { return tintColor_; }
|
||||||
|
void setFlipX(bool flip) { flipX_ = flip; }
|
||||||
|
void setFlipY(bool flip) { flipY_ = flip; }
|
||||||
|
bool isFlipX() const { return flipX_; }
|
||||||
|
bool isFlipY() const { return flipY_; }
|
||||||
|
|
||||||
|
// ------ 自动播放 ------
|
||||||
|
void setAutoPlay(bool autoPlay) { autoPlay_ = autoPlay; }
|
||||||
|
bool isAutoPlay() const { return autoPlay_; }
|
||||||
|
|
||||||
|
// ------ 碰撞盒访问 ------
|
||||||
|
const std::vector<std::array<int32_t, 6>>& getCurrentDamageBoxes() const;
|
||||||
|
const std::vector<std::array<int32_t, 6>>& getCurrentAttackBoxes() const;
|
||||||
|
|
||||||
|
// ------ 查询 ------
|
||||||
|
Size getMaxFrameSize() const;
|
||||||
|
Rect getBoundingBox() const override;
|
||||||
|
|
||||||
|
// ------ 直接访问 ------
|
||||||
|
AnimationController& getController() { return controller_; }
|
||||||
|
const AnimationController& getController() const { return controller_; }
|
||||||
|
FrameRenderer& getFrameRenderer() { return frameRenderer_; }
|
||||||
|
const FrameRenderer& getFrameRenderer() const { return frameRenderer_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onUpdate(float dt) override;
|
||||||
|
void onDraw(RenderBackend& renderer) override;
|
||||||
|
void onEnter() override;
|
||||||
|
void onExit() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
AnimationController controller_;
|
||||||
|
FrameRenderer frameRenderer_;
|
||||||
|
Color tintColor_ = Colors::White;
|
||||||
|
bool flipX_ = false;
|
||||||
|
bool flipY_ = false;
|
||||||
|
bool autoPlay_ = false;
|
||||||
|
std::vector<AnimationEventCallback> eventListeners_;
|
||||||
|
|
||||||
|
static const std::vector<std::array<int32_t, 6>> emptyBoxes_;
|
||||||
|
|
||||||
|
void setupControllerCallbacks();
|
||||||
|
void dispatchEvent(const AnimationEvent& event);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/scene/node.h>
|
||||||
|
#include <easy2d/animation/animation_node.h>
|
||||||
|
#include <easy2d/animation/als_parser.h>
|
||||||
|
#include <easy2d/animation/animation_event.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// CompositeAnimation - ALS 多层复合动画节点
|
||||||
|
// 管理多个 AnimationNode 图层,统一控制播放
|
||||||
|
// 对应 DNF 的 ALS 格式(多层动画叠加)
|
||||||
|
// ============================================================================
|
||||||
|
class CompositeAnimation : public Node {
|
||||||
|
public:
|
||||||
|
CompositeAnimation() = default;
|
||||||
|
~CompositeAnimation() override = default;
|
||||||
|
|
||||||
|
// ------ 静态工厂 ------
|
||||||
|
static Ptr<CompositeAnimation> create();
|
||||||
|
static Ptr<CompositeAnimation> create(const std::string& alsFilePath);
|
||||||
|
|
||||||
|
// ------ 加载 ------
|
||||||
|
bool loadFromFile(const std::string& alsFilePath);
|
||||||
|
|
||||||
|
// ------ 图层管理 ------
|
||||||
|
void addLayer(Ptr<AnimationNode> node, int zOrder = 0);
|
||||||
|
void removeLayer(size_t index);
|
||||||
|
Ptr<AnimationNode> getLayer(size_t index) const;
|
||||||
|
Ptr<AnimationNode> getMainLayer() const;
|
||||||
|
size_t getLayerCount() const;
|
||||||
|
|
||||||
|
// ------ 统一播放控制 ------
|
||||||
|
void play();
|
||||||
|
void pause();
|
||||||
|
void resume();
|
||||||
|
void stop();
|
||||||
|
void reset();
|
||||||
|
void setPlaybackSpeed(float speed);
|
||||||
|
void setLooping(bool loop);
|
||||||
|
|
||||||
|
bool isPlaying() const;
|
||||||
|
bool isStopped() const;
|
||||||
|
|
||||||
|
// ------ 事件回调(绑定到主图层)------
|
||||||
|
void setKeyframeCallback(KeyframeHitCallback callback);
|
||||||
|
void setCompletionCallback(AnimationCompleteCallback callback);
|
||||||
|
void addEventListener(AnimationEventCallback callback);
|
||||||
|
|
||||||
|
// ------ 视觉属性(应用到所有图层)------
|
||||||
|
void setTintColor(const Color& color);
|
||||||
|
void setFlipX(bool flip);
|
||||||
|
void setFlipY(bool flip);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct LayerEntry {
|
||||||
|
Ptr<AnimationNode> node;
|
||||||
|
int zOrder = 0;
|
||||||
|
};
|
||||||
|
std::vector<LayerEntry> layers_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,194 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/core/math_types.h>
|
||||||
|
#include <easy2d/core/color.h>
|
||||||
|
#include <variant>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <optional>
|
||||||
|
#include <any>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 帧属性键 - 强类型枚举替代原始 ANI 的字符串键
|
||||||
|
// ============================================================================
|
||||||
|
enum class FramePropertyKey : uint32 {
|
||||||
|
// 事件触发
|
||||||
|
SetFlag = 0x0001, // int: 关键帧回调索引
|
||||||
|
PlaySound = 0x0002, // string: 音效路径
|
||||||
|
|
||||||
|
// 变换属性
|
||||||
|
ImageRate = 0x0010, // Vec2: 缩放比例
|
||||||
|
ImageRotate = 0x0011, // float: 旋转角度(度)
|
||||||
|
ImageOffset = 0x0012, // Vec2: 额外位置偏移
|
||||||
|
|
||||||
|
// 视觉效果
|
||||||
|
BlendLinearDodge = 0x0020, // bool: 线性减淡
|
||||||
|
BlendAdditive = 0x0021, // bool: 加法混合
|
||||||
|
ColorTint = 0x0022, // Color: RGBA 颜色
|
||||||
|
|
||||||
|
// 控制标记
|
||||||
|
Interpolation = 0x0030, // bool: 启用到下一帧的插值
|
||||||
|
Loop = 0x0031, // bool: 全局循环标记
|
||||||
|
|
||||||
|
// DNF ANI 扩展属性
|
||||||
|
DamageType = 0x0040, // int: 伤害类型 (0=Normal, 1=SuperArmor, 2=Unbreakable)
|
||||||
|
Shadow = 0x0041, // bool: 阴影
|
||||||
|
FlipType = 0x0042, // int: 翻转类型 (1=Horizon, 2=Vertical, 3=All)
|
||||||
|
Coord = 0x0043, // int: 坐标系
|
||||||
|
LoopStart = 0x0044, // bool: 循环起始标记
|
||||||
|
LoopEnd = 0x0045, // int: 循环结束帧数
|
||||||
|
GraphicEffect = 0x0046, // int: 图形特效类型
|
||||||
|
ClipRegion = 0x0047, // vector<int>: 裁剪区域 [4个int16]
|
||||||
|
|
||||||
|
// 用户自定义扩展区间 (0x1000+)
|
||||||
|
UserDefined = 0x1000,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 帧属性值 - variant 多态值
|
||||||
|
// ============================================================================
|
||||||
|
using FramePropertyValue = std::variant<
|
||||||
|
bool,
|
||||||
|
int,
|
||||||
|
float,
|
||||||
|
std::string,
|
||||||
|
Vec2,
|
||||||
|
Color,
|
||||||
|
std::vector<int>
|
||||||
|
>;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FramePropertyKey 的 hash 支持
|
||||||
|
// ============================================================================
|
||||||
|
struct FramePropertyKeyHash {
|
||||||
|
size_t operator()(FramePropertyKey key) const noexcept {
|
||||||
|
return std::hash<uint32>{}(static_cast<uint32>(key));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FramePropertySet - 单帧属性集合
|
||||||
|
// 同时支持强类型属性和自定义扩展(不固定数据)
|
||||||
|
// ============================================================================
|
||||||
|
class FramePropertySet {
|
||||||
|
public:
|
||||||
|
FramePropertySet() = default;
|
||||||
|
|
||||||
|
// ------ 设置属性 ------
|
||||||
|
void set(FramePropertyKey key, FramePropertyValue value) {
|
||||||
|
properties_[key] = std::move(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCustom(const std::string& key, std::any value) {
|
||||||
|
customProperties_[key] = std::move(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 类型安全获取 ------
|
||||||
|
template<typename T>
|
||||||
|
std::optional<T> get(FramePropertyKey key) const {
|
||||||
|
auto it = properties_.find(key);
|
||||||
|
if (it == properties_.end()) return std::nullopt;
|
||||||
|
if (auto* val = std::get_if<T>(&it->second)) {
|
||||||
|
return *val;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T getOr(FramePropertyKey key, const T& defaultValue) const {
|
||||||
|
auto result = get<T>(key);
|
||||||
|
return result.value_or(defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::any> getCustom(const std::string& key) const {
|
||||||
|
auto it = customProperties_.find(key);
|
||||||
|
if (it == customProperties_.end()) return std::nullopt;
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 查询 ------
|
||||||
|
bool has(FramePropertyKey key) const {
|
||||||
|
return properties_.find(key) != properties_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasCustom(const std::string& key) const {
|
||||||
|
return customProperties_.find(key) != customProperties_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool empty() const {
|
||||||
|
return properties_.empty() && customProperties_.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t count() const {
|
||||||
|
return properties_.size() + customProperties_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 移除 ------
|
||||||
|
void remove(FramePropertyKey key) {
|
||||||
|
properties_.erase(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeCustom(const std::string& key) {
|
||||||
|
customProperties_.erase(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
properties_.clear();
|
||||||
|
customProperties_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 迭代 ------
|
||||||
|
using PropertyMap = std::unordered_map<FramePropertyKey, FramePropertyValue, FramePropertyKeyHash>;
|
||||||
|
const PropertyMap& properties() const { return properties_; }
|
||||||
|
|
||||||
|
// ------ 链式 API ------
|
||||||
|
FramePropertySet& withSetFlag(int index) {
|
||||||
|
set(FramePropertyKey::SetFlag, index);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
FramePropertySet& withPlaySound(const std::string& path) {
|
||||||
|
set(FramePropertyKey::PlaySound, path);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
FramePropertySet& withImageRate(const Vec2& scale) {
|
||||||
|
set(FramePropertyKey::ImageRate, scale);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
FramePropertySet& withImageRotate(float degrees) {
|
||||||
|
set(FramePropertyKey::ImageRotate, degrees);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
FramePropertySet& withColorTint(const Color& color) {
|
||||||
|
set(FramePropertyKey::ColorTint, color);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
FramePropertySet& withInterpolation(bool enabled = true) {
|
||||||
|
set(FramePropertyKey::Interpolation, enabled);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
FramePropertySet& withBlendLinearDodge(bool enabled = true) {
|
||||||
|
set(FramePropertyKey::BlendLinearDodge, enabled);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
FramePropertySet& withLoop(bool enabled = true) {
|
||||||
|
set(FramePropertyKey::Loop, enabled);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
PropertyMap properties_;
|
||||||
|
std::unordered_map<std::string, std::any> customProperties_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/animation/animation_frame.h>
|
||||||
|
#include <easy2d/animation/sprite_frame.h>
|
||||||
|
#include <easy2d/animation/sprite_frame_cache.h>
|
||||||
|
#include <easy2d/animation/interpolation_engine.h>
|
||||||
|
#include <easy2d/graphics/render_backend.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FrameRenderer - 帧渲染器
|
||||||
|
// 单渲染器 + SpriteFrame 引用策略,替代 N帧=N个Sprite 的旧设计
|
||||||
|
// 负责预加载帧的 SpriteFrame、渲染当前帧、处理混合模式
|
||||||
|
// ============================================================================
|
||||||
|
class FrameRenderer {
|
||||||
|
public:
|
||||||
|
FrameRenderer() = default;
|
||||||
|
|
||||||
|
// ------ 预加载 ------
|
||||||
|
// 解析所有帧的 SpriteFrame(通过 SpriteFrameCache)
|
||||||
|
bool preloadFrames(const std::vector<AnimationFrame>& frames);
|
||||||
|
void releaseFrames();
|
||||||
|
|
||||||
|
// ------ 渲染当前帧 ------
|
||||||
|
void renderFrame(RenderBackend& renderer,
|
||||||
|
const AnimationFrame& frame,
|
||||||
|
size_t frameIndex,
|
||||||
|
const Vec2& position,
|
||||||
|
float nodeOpacity,
|
||||||
|
const Color& tintColor,
|
||||||
|
bool flipX, bool flipY);
|
||||||
|
|
||||||
|
// ------ 渲染插值帧 ------
|
||||||
|
void renderInterpolated(RenderBackend& renderer,
|
||||||
|
const AnimationFrame& fromFrame,
|
||||||
|
size_t fromIndex,
|
||||||
|
const InterpolatedProperties& props,
|
||||||
|
const Vec2& position,
|
||||||
|
float nodeOpacity,
|
||||||
|
const Color& tintColor,
|
||||||
|
bool flipX, bool flipY);
|
||||||
|
|
||||||
|
// ------ 混合模式映射 ------
|
||||||
|
static BlendMode mapBlendMode(const FramePropertySet& props);
|
||||||
|
|
||||||
|
// ------ 查询 ------
|
||||||
|
Ptr<SpriteFrame> getSpriteFrame(size_t frameIndex) const;
|
||||||
|
Size getMaxFrameSize() const { return maxFrameSize_; }
|
||||||
|
bool isLoaded() const { return !spriteFrames_.empty(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Ptr<SpriteFrame>> spriteFrames_;
|
||||||
|
Size maxFrameSize_;
|
||||||
|
|
||||||
|
void drawSpriteFrame(RenderBackend& renderer,
|
||||||
|
Ptr<SpriteFrame> sf,
|
||||||
|
const Vec2& position,
|
||||||
|
const Vec2& offset,
|
||||||
|
const Vec2& scale,
|
||||||
|
float rotation,
|
||||||
|
float opacity,
|
||||||
|
const Color& tint,
|
||||||
|
bool flipX, bool flipY,
|
||||||
|
BlendMode blend);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/core/math_types.h>
|
||||||
|
#include <easy2d/core/color.h>
|
||||||
|
#include <easy2d/animation/animation_frame.h>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 插值结果 - 两帧之间的插值后属性
|
||||||
|
// ============================================================================
|
||||||
|
struct InterpolatedProperties {
|
||||||
|
Vec2 position;
|
||||||
|
Vec2 scale = Vec2::One();
|
||||||
|
float rotation = 0.0f;
|
||||||
|
Color color = Colors::White;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 插值曲线类型
|
||||||
|
// ============================================================================
|
||||||
|
enum class InterpolationCurve : uint8 {
|
||||||
|
Linear, // 线性(原始系统的 uniform velocity)
|
||||||
|
EaseIn, // 缓入
|
||||||
|
EaseOut, // 缓出
|
||||||
|
EaseInOut, // 缓入缓出
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// InterpolationEngine - 帧间属性插值计算(静态方法,无状态)
|
||||||
|
// 独立于 AnimationController,可复用于其他系统
|
||||||
|
// ============================================================================
|
||||||
|
class InterpolationEngine {
|
||||||
|
public:
|
||||||
|
/// 核心插值计算:根据 t 因子 (0~1) 计算两帧之间的插值属性
|
||||||
|
static InterpolatedProperties interpolate(
|
||||||
|
const AnimationFrame& from,
|
||||||
|
const AnimationFrame& to,
|
||||||
|
float t,
|
||||||
|
InterpolationCurve curve = InterpolationCurve::Linear)
|
||||||
|
{
|
||||||
|
float curvedT = applyCurve(t, curve);
|
||||||
|
|
||||||
|
InterpolatedProperties result;
|
||||||
|
result.position = lerpPosition(from, to, curvedT);
|
||||||
|
result.scale = lerpScale(from, to, curvedT);
|
||||||
|
result.rotation = lerpRotation(from, to, curvedT);
|
||||||
|
result.color = lerpColor(from, to, curvedT);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 位置插值
|
||||||
|
static Vec2 lerpPosition(const AnimationFrame& from, const AnimationFrame& to, float t) {
|
||||||
|
return Vec2::lerp(from.offset, to.offset, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 缩放插值
|
||||||
|
static Vec2 lerpScale(const AnimationFrame& from, const AnimationFrame& to, float t) {
|
||||||
|
Vec2 fromScale = from.getEffectiveScale();
|
||||||
|
Vec2 toScale = to.getEffectiveScale();
|
||||||
|
return Vec2::lerp(fromScale, toScale, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 旋转插值
|
||||||
|
static float lerpRotation(const AnimationFrame& from, const AnimationFrame& to, float t) {
|
||||||
|
float fromRot = from.getEffectiveRotation();
|
||||||
|
float toRot = to.getEffectiveRotation();
|
||||||
|
return math::lerp(fromRot, toRot, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 颜色插值
|
||||||
|
static Color lerpColor(const AnimationFrame& from, const AnimationFrame& to, float t) {
|
||||||
|
Color fromColor = from.getEffectiveColor();
|
||||||
|
Color toColor = to.getEffectiveColor();
|
||||||
|
return Color::lerp(fromColor, toColor, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 应用曲线函数
|
||||||
|
static float applyCurve(float t, InterpolationCurve curve) {
|
||||||
|
t = math::clamp(t, 0.0f, 1.0f);
|
||||||
|
|
||||||
|
switch (curve) {
|
||||||
|
case InterpolationCurve::Linear:
|
||||||
|
return t;
|
||||||
|
|
||||||
|
case InterpolationCurve::EaseIn:
|
||||||
|
return t * t;
|
||||||
|
|
||||||
|
case InterpolationCurve::EaseOut:
|
||||||
|
return t * (2.0f - t);
|
||||||
|
|
||||||
|
case InterpolationCurve::EaseInOut:
|
||||||
|
if (t < 0.5f)
|
||||||
|
return 2.0f * t * t;
|
||||||
|
else
|
||||||
|
return -1.0f + (4.0f - 2.0f * t) * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/core/math_types.h>
|
||||||
|
#include <easy2d/graphics/texture.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// SpriteFrame - 精灵帧(纹理 + 区域 + 偏移的中间抽象)
|
||||||
|
// 借鉴 Cocos2d-x SpriteFrame:解耦纹理物理存储与逻辑帧
|
||||||
|
// 一个纹理图集可包含多个 SpriteFrame,减少纹理切换提升渲染性能
|
||||||
|
// ============================================================================
|
||||||
|
class SpriteFrame {
|
||||||
|
public:
|
||||||
|
SpriteFrame() = default;
|
||||||
|
|
||||||
|
SpriteFrame(Ptr<Texture> texture, const Rect& rect)
|
||||||
|
: texture_(std::move(texture))
|
||||||
|
, rect_(rect)
|
||||||
|
, originalSize_(rect.size) {}
|
||||||
|
|
||||||
|
SpriteFrame(Ptr<Texture> texture, const Rect& rect,
|
||||||
|
const Vec2& offset, const Size& originalSize)
|
||||||
|
: texture_(std::move(texture))
|
||||||
|
, rect_(rect)
|
||||||
|
, offset_(offset)
|
||||||
|
, originalSize_(originalSize) {}
|
||||||
|
|
||||||
|
// ------ 静态创建 ------
|
||||||
|
static Ptr<SpriteFrame> create(Ptr<Texture> texture, const Rect& rect) {
|
||||||
|
return makePtr<SpriteFrame>(std::move(texture), rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Ptr<SpriteFrame> create(Ptr<Texture> texture, const Rect& rect,
|
||||||
|
const Vec2& offset, const Size& originalSize) {
|
||||||
|
return makePtr<SpriteFrame>(std::move(texture), rect, offset, originalSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 纹理信息 ------
|
||||||
|
void setTexture(Ptr<Texture> texture) { texture_ = std::move(texture); }
|
||||||
|
Ptr<Texture> getTexture() const { return texture_; }
|
||||||
|
|
||||||
|
// ------ 矩形区域(在纹理图集中的位置)------
|
||||||
|
void setRect(const Rect& rect) { rect_ = rect; }
|
||||||
|
const Rect& getRect() const { return rect_; }
|
||||||
|
|
||||||
|
// ------ 偏移(图集打包时的裁剪偏移)------
|
||||||
|
void setOffset(const Vec2& offset) { offset_ = offset; }
|
||||||
|
const Vec2& getOffset() const { return offset_; }
|
||||||
|
|
||||||
|
// ------ 原始尺寸(裁剪前的完整尺寸)------
|
||||||
|
void setOriginalSize(const Size& size) { originalSize_ = size; }
|
||||||
|
const Size& getOriginalSize() const { return originalSize_; }
|
||||||
|
|
||||||
|
// ------ 旋转标志(图集工具可能旋转90度)------
|
||||||
|
void setRotated(bool rotated) { rotated_ = rotated; }
|
||||||
|
bool isRotated() const { return rotated_; }
|
||||||
|
|
||||||
|
// ------ 名称(用于缓存索引)------
|
||||||
|
void setName(const std::string& name) { name_ = name; }
|
||||||
|
const std::string& getName() const { return name_; }
|
||||||
|
|
||||||
|
// ------ 有效性检查 ------
|
||||||
|
bool isValid() const { return texture_ != nullptr; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ptr<Texture> texture_;
|
||||||
|
Rect rect_;
|
||||||
|
Vec2 offset_;
|
||||||
|
Size originalSize_;
|
||||||
|
bool rotated_ = false;
|
||||||
|
std::string name_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,169 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/animation/sprite_frame.h>
|
||||||
|
#include <easy2d/graphics/texture_pool.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// SpriteFrameCache - 精灵帧全局缓存(借鉴 Cocos SpriteFrameCache)
|
||||||
|
// 全局单例管理所有精灵帧,避免重复创建,支持图集自动切割
|
||||||
|
// ============================================================================
|
||||||
|
class SpriteFrameCache {
|
||||||
|
public:
|
||||||
|
static SpriteFrameCache& getInstance() {
|
||||||
|
static SpriteFrameCache instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 添加帧 ------
|
||||||
|
|
||||||
|
/// 添加单个精灵帧
|
||||||
|
void addSpriteFrame(Ptr<SpriteFrame> frame, const std::string& name) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
frames_[name] = std::move(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 从纹理和矩形区域创建并添加帧
|
||||||
|
void addSpriteFrameFromTexture(Ptr<Texture> texture, const Rect& rect,
|
||||||
|
const std::string& name) {
|
||||||
|
auto frame = SpriteFrame::create(std::move(texture), rect);
|
||||||
|
frame->setName(name);
|
||||||
|
addSpriteFrame(std::move(frame), name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 从纹理图集批量切割添加(等宽等高网格)
|
||||||
|
void addSpriteFramesFromGrid(const std::string& texturePath,
|
||||||
|
int frameWidth, int frameHeight,
|
||||||
|
int frameCount = -1,
|
||||||
|
int spacing = 0, int margin = 0) {
|
||||||
|
auto texture = TexturePool::getInstance().get(texturePath);
|
||||||
|
if (!texture) return;
|
||||||
|
addSpriteFramesFromGrid(texture, texturePath, frameWidth, frameHeight,
|
||||||
|
frameCount, spacing, margin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 从纹理对象批量切割添加(等宽等高网格,无需走 TexturePool)
|
||||||
|
void addSpriteFramesFromGrid(Ptr<Texture> texture, const std::string& keyPrefix,
|
||||||
|
int frameWidth, int frameHeight,
|
||||||
|
int frameCount = -1,
|
||||||
|
int spacing = 0, int margin = 0) {
|
||||||
|
if (!texture) return;
|
||||||
|
|
||||||
|
int texW = texture->getWidth();
|
||||||
|
int texH = texture->getHeight();
|
||||||
|
int usableW = texW - 2 * margin;
|
||||||
|
int usableH = texH - 2 * margin;
|
||||||
|
int cols = (usableW + spacing) / (frameWidth + spacing);
|
||||||
|
int rows = (usableH + spacing) / (frameHeight + spacing);
|
||||||
|
int total = (frameCount > 0) ? frameCount : cols * rows;
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
for (int i = 0; i < total; ++i) {
|
||||||
|
int col = i % cols;
|
||||||
|
int row = i / cols;
|
||||||
|
if (row >= rows) break;
|
||||||
|
|
||||||
|
Rect rect(
|
||||||
|
static_cast<float>(margin + col * (frameWidth + spacing)),
|
||||||
|
static_cast<float>(margin + row * (frameHeight + spacing)),
|
||||||
|
static_cast<float>(frameWidth),
|
||||||
|
static_cast<float>(frameHeight)
|
||||||
|
);
|
||||||
|
|
||||||
|
std::string name = keyPrefix + "#" + std::to_string(i);
|
||||||
|
auto frame = SpriteFrame::create(texture, rect);
|
||||||
|
frame->setName(name);
|
||||||
|
frames_[name] = std::move(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 获取帧 ------
|
||||||
|
|
||||||
|
/// 按名称获取
|
||||||
|
Ptr<SpriteFrame> getSpriteFrame(const std::string& name) const {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
auto it = frames_.find(name);
|
||||||
|
if (it != frames_.end()) return it->second;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 通过路径+索引获取或创建(ANI 格式的定位方式)
|
||||||
|
Ptr<SpriteFrame> getOrCreateFromFile(const std::string& texturePath,
|
||||||
|
int index = 0) {
|
||||||
|
std::string key = texturePath + "#" + std::to_string(index);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
auto it = frames_.find(key);
|
||||||
|
if (it != frames_.end()) return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 缓存未命中,从 TexturePool 加载纹理并创建 SpriteFrame
|
||||||
|
auto texture = TexturePool::getInstance().get(texturePath);
|
||||||
|
if (!texture) return nullptr;
|
||||||
|
|
||||||
|
// 默认整张纹理作为一帧(index=0),或用整张纹理
|
||||||
|
Rect rect(0.0f, 0.0f,
|
||||||
|
static_cast<float>(texture->getWidth()),
|
||||||
|
static_cast<float>(texture->getHeight()));
|
||||||
|
|
||||||
|
auto frame = SpriteFrame::create(texture, rect);
|
||||||
|
frame->setName(key);
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
frames_[key] = frame;
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------ 缓存管理 ------
|
||||||
|
|
||||||
|
bool has(const std::string& name) const {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
return frames_.find(name) != frames_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeSpriteFrame(const std::string& name) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
frames_.erase(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 移除未被外部引用的精灵帧(use_count == 1 表示仅缓存自身持有)
|
||||||
|
void removeUnusedSpriteFrames() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
for (auto it = frames_.begin(); it != frames_.end(); ) {
|
||||||
|
if (it->second.use_count() == 1) {
|
||||||
|
it = frames_.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
frames_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t count() const {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
return frames_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SpriteFrameCache() = default;
|
||||||
|
~SpriteFrameCache() = default;
|
||||||
|
SpriteFrameCache(const SpriteFrameCache&) = delete;
|
||||||
|
SpriteFrameCache& operator=(const SpriteFrameCache&) = delete;
|
||||||
|
|
||||||
|
mutable std::mutex mutex_;
|
||||||
|
std::unordered_map<std::string, Ptr<SpriteFrame>> frames_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 便捷宏
|
||||||
|
#define E2D_SPRITE_FRAME_CACHE() ::easy2d::SpriteFrameCache::getInstance()
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/core/string.h>
|
||||||
|
#include <easy2d/graphics/render_backend.h>
|
||||||
|
#include <easy2d/platform/window.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
class Input;
|
||||||
|
class AudioEngine;
|
||||||
|
class SceneManager;
|
||||||
|
class ResourceManager;
|
||||||
|
class TimerManager;
|
||||||
|
class EventQueue;
|
||||||
|
class EventDispatcher;
|
||||||
|
class Camera;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Application 配置
|
||||||
|
// ============================================================================
|
||||||
|
struct AppConfig {
|
||||||
|
String title = "Easy2D Application";
|
||||||
|
int width = 800;
|
||||||
|
int height = 600;
|
||||||
|
bool fullscreen = false;
|
||||||
|
bool resizable = true; // 窗口是否可调整大小
|
||||||
|
bool vsync = true;
|
||||||
|
int fpsLimit = 0; // 0 = 不限制
|
||||||
|
BackendType renderBackend = BackendType::OpenGL;
|
||||||
|
int msaaSamples = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Application 单例 - 应用主控
|
||||||
|
// ============================================================================
|
||||||
|
class Application {
|
||||||
|
public:
|
||||||
|
// Meyer's 单例
|
||||||
|
static Application& instance();
|
||||||
|
|
||||||
|
// 禁止拷贝
|
||||||
|
Application(const Application&) = delete;
|
||||||
|
Application& operator=(const Application&) = delete;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 生命周期
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool init(const AppConfig& config);
|
||||||
|
void shutdown();
|
||||||
|
void run();
|
||||||
|
void quit();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 状态控制
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void pause();
|
||||||
|
void resume();
|
||||||
|
bool isPaused() const { return paused_; }
|
||||||
|
bool isRunning() const { return running_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 子系统访问
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
Window& window() { return *window_; }
|
||||||
|
RenderBackend& renderer() { return *renderer_; }
|
||||||
|
Input& input();
|
||||||
|
AudioEngine& audio();
|
||||||
|
SceneManager& scenes();
|
||||||
|
ResourceManager& resources();
|
||||||
|
TimerManager& timers();
|
||||||
|
EventQueue& eventQueue();
|
||||||
|
EventDispatcher& eventDispatcher();
|
||||||
|
Camera& camera();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 便捷方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void enterScene(Ptr<class Scene> scene);
|
||||||
|
void enterScene(Ptr<class Scene> scene, Ptr<class Transition> transition);
|
||||||
|
|
||||||
|
float deltaTime() const { return deltaTime_; }
|
||||||
|
float totalTime() const { return totalTime_; }
|
||||||
|
int fps() const { return currentFps_; }
|
||||||
|
|
||||||
|
// 获取配置
|
||||||
|
const AppConfig& getConfig() const { return config_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Application() = default;
|
||||||
|
~Application();
|
||||||
|
|
||||||
|
void mainLoop();
|
||||||
|
void update();
|
||||||
|
void render();
|
||||||
|
|
||||||
|
// 配置
|
||||||
|
AppConfig config_;
|
||||||
|
|
||||||
|
// 子系统
|
||||||
|
UniquePtr<Window> window_;
|
||||||
|
UniquePtr<RenderBackend> renderer_;
|
||||||
|
UniquePtr<SceneManager> sceneManager_;
|
||||||
|
UniquePtr<ResourceManager> resourceManager_;
|
||||||
|
UniquePtr<TimerManager> timerManager_;
|
||||||
|
UniquePtr<EventQueue> eventQueue_;
|
||||||
|
UniquePtr<EventDispatcher> eventDispatcher_;
|
||||||
|
UniquePtr<Camera> camera_;
|
||||||
|
|
||||||
|
// 状态
|
||||||
|
bool initialized_ = false;
|
||||||
|
bool running_ = false;
|
||||||
|
bool paused_ = false;
|
||||||
|
bool shouldQuit_ = false;
|
||||||
|
|
||||||
|
// 时间
|
||||||
|
float deltaTime_ = 0.0f;
|
||||||
|
float totalTime_ = 0.0f;
|
||||||
|
double lastFrameTime_ = 0.0;
|
||||||
|
int frameCount_ = 0;
|
||||||
|
float fpsTimer_ = 0.0f;
|
||||||
|
int currentFps_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
class Sound;
|
||||||
|
|
||||||
|
class AudioEngine {
|
||||||
|
public:
|
||||||
|
static AudioEngine& getInstance();
|
||||||
|
|
||||||
|
AudioEngine(const AudioEngine&) = delete;
|
||||||
|
AudioEngine& operator=(const AudioEngine&) = delete;
|
||||||
|
AudioEngine(AudioEngine&&) = delete;
|
||||||
|
AudioEngine& operator=(AudioEngine&&) = delete;
|
||||||
|
|
||||||
|
bool initialize();
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
std::shared_ptr<Sound> loadSound(const std::string& filePath);
|
||||||
|
std::shared_ptr<Sound> loadSound(const std::string& name, const std::string& filePath);
|
||||||
|
|
||||||
|
std::shared_ptr<Sound> getSound(const std::string& name);
|
||||||
|
void unloadSound(const std::string& name);
|
||||||
|
void unloadAllSounds();
|
||||||
|
|
||||||
|
void setMasterVolume(float volume);
|
||||||
|
float getMasterVolume() const;
|
||||||
|
|
||||||
|
void pauseAll();
|
||||||
|
void resumeAll();
|
||||||
|
void stopAll();
|
||||||
|
|
||||||
|
private:
|
||||||
|
AudioEngine() = default;
|
||||||
|
~AudioEngine();
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::shared_ptr<Sound>> sounds_;
|
||||||
|
float masterVolume_ = 1.0f;
|
||||||
|
bool initialized_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
|
||||||
|
struct Mix_Chunk;
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
class AudioEngine;
|
||||||
|
|
||||||
|
class Sound {
|
||||||
|
public:
|
||||||
|
~Sound();
|
||||||
|
|
||||||
|
Sound(const Sound&) = delete;
|
||||||
|
Sound& operator=(const Sound&) = delete;
|
||||||
|
|
||||||
|
bool play();
|
||||||
|
void pause();
|
||||||
|
void resume();
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
bool isPlaying() const;
|
||||||
|
bool isPaused() const;
|
||||||
|
|
||||||
|
void setVolume(float volume);
|
||||||
|
float getVolume() const { return volume_; }
|
||||||
|
|
||||||
|
void setLooping(bool looping);
|
||||||
|
bool isLooping() const { return looping_; }
|
||||||
|
|
||||||
|
void setPitch(float pitch);
|
||||||
|
float getPitch() const { return pitch_; }
|
||||||
|
|
||||||
|
float getDuration() const;
|
||||||
|
float getCursor() const;
|
||||||
|
void setCursor(float seconds);
|
||||||
|
|
||||||
|
const std::string& getFilePath() const { return filePath_; }
|
||||||
|
const std::string& getName() const { return name_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class AudioEngine;
|
||||||
|
|
||||||
|
Sound(const std::string& name, const std::string& filePath, Mix_Chunk* chunk);
|
||||||
|
|
||||||
|
std::string name_;
|
||||||
|
std::string filePath_;
|
||||||
|
Mix_Chunk* chunk_ = nullptr;
|
||||||
|
int channel_ = -1; // SDL_mixer 分配的通道,-1 表示未播放
|
||||||
|
float volume_ = 1.0f;
|
||||||
|
float pitch_ = 1.0f;
|
||||||
|
bool looping_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <glm/vec4.hpp>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
/// RGBA 颜色(浮点数,每通道 0.0 - 1.0)
|
||||||
|
struct Color {
|
||||||
|
float r = 0.0f;
|
||||||
|
float g = 0.0f;
|
||||||
|
float b = 0.0f;
|
||||||
|
float a = 1.0f;
|
||||||
|
|
||||||
|
constexpr Color() = default;
|
||||||
|
|
||||||
|
constexpr Color(float r, float g, float b, float a = 1.0f)
|
||||||
|
: r(r), g(g), b(b), a(a) {}
|
||||||
|
|
||||||
|
/// 从 0xRRGGBB 整数构造
|
||||||
|
constexpr explicit Color(uint32_t rgb, float a = 1.0f)
|
||||||
|
: r(static_cast<float>((rgb >> 16) & 0xFF) / 255.0f)
|
||||||
|
, g(static_cast<float>((rgb >> 8) & 0xFF) / 255.0f)
|
||||||
|
, b(static_cast<float>((rgb) & 0xFF) / 255.0f)
|
||||||
|
, a(a) {}
|
||||||
|
|
||||||
|
/// 从 0-255 整数构造
|
||||||
|
static constexpr Color fromRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) {
|
||||||
|
return Color(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 转换为 glm::vec4
|
||||||
|
glm::vec4 toVec4() const { return {r, g, b, a}; }
|
||||||
|
|
||||||
|
/// 线性插值
|
||||||
|
static Color lerp(const Color& a, const Color& b, float t) {
|
||||||
|
t = std::clamp(t, 0.0f, 1.0f);
|
||||||
|
return Color(
|
||||||
|
a.r + (b.r - a.r) * t,
|
||||||
|
a.g + (b.g - a.g) * t,
|
||||||
|
a.b + (b.b - a.b) * t,
|
||||||
|
a.a + (b.a - a.a) * t
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const Color& other) const {
|
||||||
|
return r == other.r && g == other.g && b == other.b && a == other.a;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const Color& other) const {
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 算术运算符
|
||||||
|
Color operator+(const Color& other) const {
|
||||||
|
return Color(r + other.r, g + other.g, b + other.b, a + other.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color operator-(const Color& other) const {
|
||||||
|
return Color(r - other.r, g - other.g, b - other.b, a - other.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color operator*(float scalar) const {
|
||||||
|
return Color(r * scalar, g * scalar, b * scalar, a * scalar);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color operator/(float scalar) const {
|
||||||
|
return Color(r / scalar, g / scalar, b / scalar, a / scalar);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color& operator+=(const Color& other) {
|
||||||
|
r += other.r; g += other.g; b += other.b; a += other.a;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color& operator-=(const Color& other) {
|
||||||
|
r -= other.r; g -= other.g; b -= other.b; a -= other.a;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color& operator*=(float scalar) {
|
||||||
|
r *= scalar; g *= scalar; b *= scalar; a *= scalar;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color& operator/=(float scalar) {
|
||||||
|
r /= scalar; g /= scalar; b /= scalar; a /= scalar;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 命名颜色常量
|
||||||
|
namespace Colors {
|
||||||
|
inline constexpr Color White {1.0f, 1.0f, 1.0f, 1.0f};
|
||||||
|
inline constexpr Color Black {0.0f, 0.0f, 0.0f, 1.0f};
|
||||||
|
inline constexpr Color Red {1.0f, 0.0f, 0.0f, 1.0f};
|
||||||
|
inline constexpr Color Green {0.0f, 1.0f, 0.0f, 1.0f};
|
||||||
|
inline constexpr Color Blue {0.0f, 0.0f, 1.0f, 1.0f};
|
||||||
|
inline constexpr Color Yellow {1.0f, 1.0f, 0.0f, 1.0f};
|
||||||
|
inline constexpr Color Cyan {0.0f, 1.0f, 1.0f, 1.0f};
|
||||||
|
inline constexpr Color Magenta {1.0f, 0.0f, 1.0f, 1.0f};
|
||||||
|
inline constexpr Color Orange {1.0f, 0.647f, 0.0f, 1.0f};
|
||||||
|
inline constexpr Color Purple {0.502f, 0.0f, 0.502f, 1.0f};
|
||||||
|
inline constexpr Color Pink {1.0f, 0.753f, 0.796f, 1.0f};
|
||||||
|
inline constexpr Color Gray {0.502f, 0.502f, 0.502f, 1.0f};
|
||||||
|
inline constexpr Color LightGray {0.827f, 0.827f, 0.827f, 1.0f};
|
||||||
|
inline constexpr Color DarkGray {0.412f, 0.412f, 0.412f, 1.0f};
|
||||||
|
inline constexpr Color Brown {0.647f, 0.165f, 0.165f, 1.0f};
|
||||||
|
inline constexpr Color Gold {1.0f, 0.843f, 0.0f, 1.0f};
|
||||||
|
inline constexpr Color Silver {0.753f, 0.753f, 0.753f, 1.0f};
|
||||||
|
inline constexpr Color SkyBlue {0.529f, 0.808f, 0.922f, 1.0f};
|
||||||
|
inline constexpr Color LimeGreen {0.196f, 0.804f, 0.196f, 1.0f};
|
||||||
|
inline constexpr Color Coral {1.0f, 0.498f, 0.314f, 1.0f};
|
||||||
|
inline constexpr Color Transparent {0.0f, 0.0f, 0.0f, 0.0f};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为了向后兼容,在 Color 结构体内提供静态引用
|
||||||
|
struct ColorConstants {
|
||||||
|
static const Color& White;
|
||||||
|
static const Color& Black;
|
||||||
|
static const Color& Red;
|
||||||
|
static const Color& Green;
|
||||||
|
static const Color& Blue;
|
||||||
|
static const Color& Yellow;
|
||||||
|
static const Color& Cyan;
|
||||||
|
static const Color& Magenta;
|
||||||
|
static const Color& Transparent;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,295 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <glm/vec2.hpp>
|
||||||
|
#include <glm/mat4x4.hpp>
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
#include <cmath>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 常量
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
constexpr float PI_F = 3.14159265358979323846f;
|
||||||
|
constexpr float DEG_TO_RAD = PI_F / 180.0f;
|
||||||
|
constexpr float RAD_TO_DEG = 180.0f / PI_F;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 2D 向量
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
struct Vec2 {
|
||||||
|
float x = 0.0f;
|
||||||
|
float y = 0.0f;
|
||||||
|
|
||||||
|
constexpr Vec2() = default;
|
||||||
|
constexpr Vec2(float x, float y) : x(x), y(y) {}
|
||||||
|
explicit Vec2(const glm::vec2& v) : x(v.x), y(v.y) {}
|
||||||
|
|
||||||
|
glm::vec2 toGlm() const { return {x, y}; }
|
||||||
|
static Vec2 fromGlm(const glm::vec2& v) { return {v.x, v.y}; }
|
||||||
|
|
||||||
|
// 基础运算
|
||||||
|
Vec2 operator+(const Vec2& v) const { return {x + v.x, y + v.y}; }
|
||||||
|
Vec2 operator-(const Vec2& v) const { return {x - v.x, y - v.y}; }
|
||||||
|
Vec2 operator*(float s) const { return {x * s, y * s}; }
|
||||||
|
Vec2 operator/(float s) const { return {x / s, y / s}; }
|
||||||
|
Vec2 operator-() const { return {-x, -y}; }
|
||||||
|
|
||||||
|
Vec2& operator+=(const Vec2& v) { x += v.x; y += v.y; return *this; }
|
||||||
|
Vec2& operator-=(const Vec2& v) { x -= v.x; y -= v.y; return *this; }
|
||||||
|
Vec2& operator*=(float s) { x *= s; y *= s; return *this; }
|
||||||
|
Vec2& operator/=(float s) { x /= s; y /= s; return *this; }
|
||||||
|
|
||||||
|
bool operator==(const Vec2& v) const { return x == v.x && y == v.y; }
|
||||||
|
bool operator!=(const Vec2& v) const { return !(*this == v); }
|
||||||
|
|
||||||
|
// 向量运算
|
||||||
|
float length() const { return std::sqrt(x * x + y * y); }
|
||||||
|
float lengthSquared() const { return x * x + y * y; }
|
||||||
|
|
||||||
|
Vec2 normalized() const {
|
||||||
|
float len = length();
|
||||||
|
if (len > 0.0f) return {x / len, y / len};
|
||||||
|
return {0.0f, 0.0f};
|
||||||
|
}
|
||||||
|
|
||||||
|
float dot(const Vec2& v) const { return x * v.x + y * v.y; }
|
||||||
|
float cross(const Vec2& v) const { return x * v.y - y * v.x; }
|
||||||
|
|
||||||
|
float distance(const Vec2& v) const { return (*this - v).length(); }
|
||||||
|
float angle() const { return std::atan2(y, x) * RAD_TO_DEG; }
|
||||||
|
|
||||||
|
static Vec2 lerp(const Vec2& a, const Vec2& b, float t) {
|
||||||
|
return a + (b - a) * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr Vec2 Zero() { return {0.0f, 0.0f}; }
|
||||||
|
static constexpr Vec2 One() { return {1.0f, 1.0f}; }
|
||||||
|
static constexpr Vec2 UnitX() { return {1.0f, 0.0f}; }
|
||||||
|
static constexpr Vec2 UnitY() { return {0.0f, 1.0f}; }
|
||||||
|
};
|
||||||
|
|
||||||
|
inline Vec2 operator*(float s, const Vec2& v) { return v * s; }
|
||||||
|
|
||||||
|
using Point = Vec2;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 3D 向量 (用于3D动作)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
struct Vec3 {
|
||||||
|
float x = 0.0f;
|
||||||
|
float y = 0.0f;
|
||||||
|
float z = 0.0f;
|
||||||
|
|
||||||
|
constexpr Vec3() = default;
|
||||||
|
constexpr Vec3(float x, float y, float z) : x(x), y(y), z(z) {}
|
||||||
|
explicit Vec3(const glm::vec3& v) : x(v.x), y(v.y), z(v.z) {}
|
||||||
|
|
||||||
|
glm::vec3 toGlm() const { return {x, y, z}; }
|
||||||
|
static Vec3 fromGlm(const glm::vec3& v) { return {v.x, v.y, v.z}; }
|
||||||
|
|
||||||
|
Vec3 operator+(const Vec3& v) const { return {x + v.x, y + v.y, z + v.z}; }
|
||||||
|
Vec3 operator-(const Vec3& v) const { return {x - v.x, y - v.y, z - v.z}; }
|
||||||
|
Vec3 operator*(float s) const { return {x * s, y * s, z * s}; }
|
||||||
|
Vec3 operator/(float s) const { return {x / s, y / s, z / s}; }
|
||||||
|
Vec3 operator-() const { return {-x, -y, -z}; }
|
||||||
|
|
||||||
|
Vec3& operator+=(const Vec3& v) { x += v.x; y += v.y; z += v.z; return *this; }
|
||||||
|
Vec3& operator-=(const Vec3& v) { x -= v.x; y -= v.y; z -= v.z; return *this; }
|
||||||
|
Vec3& operator*=(float s) { x *= s; y *= s; z *= s; return *this; }
|
||||||
|
Vec3& operator/=(float s) { x /= s; y /= s; z /= s; return *this; }
|
||||||
|
|
||||||
|
bool operator==(const Vec3& v) const { return x == v.x && y == v.y && z == v.z; }
|
||||||
|
bool operator!=(const Vec3& v) const { return !(*this == v); }
|
||||||
|
|
||||||
|
float length() const { return std::sqrt(x * x + y * y + z * z); }
|
||||||
|
float lengthSquared() const { return x * x + y * y + z * z; }
|
||||||
|
|
||||||
|
Vec3 normalized() const {
|
||||||
|
float len = length();
|
||||||
|
if (len > 0.0f) return {x / len, y / len, z / len};
|
||||||
|
return {0.0f, 0.0f, 0.0f};
|
||||||
|
}
|
||||||
|
|
||||||
|
float dot(const Vec3& v) const { return x * v.x + y * v.y + z * v.z; }
|
||||||
|
|
||||||
|
static Vec3 lerp(const Vec3& a, const Vec3& b, float t) {
|
||||||
|
return a + (b - a) * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr Vec3 Zero() { return {0.0f, 0.0f, 0.0f}; }
|
||||||
|
static constexpr Vec3 One() { return {1.0f, 1.0f, 1.0f}; }
|
||||||
|
};
|
||||||
|
|
||||||
|
inline Vec3 operator*(float s, const Vec3& v) { return v * s; }
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 2D 尺寸
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
struct Size {
|
||||||
|
float width = 0.0f;
|
||||||
|
float height = 0.0f;
|
||||||
|
|
||||||
|
constexpr Size() = default;
|
||||||
|
constexpr Size(float w, float h) : width(w), height(h) {}
|
||||||
|
|
||||||
|
bool operator==(const Size& s) const { return width == s.width && height == s.height; }
|
||||||
|
bool operator!=(const Size& s) const { return !(*this == s); }
|
||||||
|
|
||||||
|
float area() const { return width * height; }
|
||||||
|
bool empty() const { return width <= 0.0f || height <= 0.0f; }
|
||||||
|
|
||||||
|
static constexpr Size Zero() { return {0.0f, 0.0f}; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 2D 矩形
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
struct Rect {
|
||||||
|
Point origin;
|
||||||
|
Size size;
|
||||||
|
|
||||||
|
constexpr Rect() = default;
|
||||||
|
constexpr Rect(float x, float y, float w, float h) : origin(x, y), size(w, h) {}
|
||||||
|
constexpr Rect(const Point& o, const Size& s) : origin(o), size(s) {}
|
||||||
|
|
||||||
|
float left() const { return origin.x; }
|
||||||
|
float top() const { return origin.y; }
|
||||||
|
float right() const { return origin.x + size.width; }
|
||||||
|
float bottom() const { return origin.y + size.height; }
|
||||||
|
float width() const { return size.width; }
|
||||||
|
float height() const { return size.height; }
|
||||||
|
Point center() const { return {origin.x + size.width * 0.5f, origin.y + size.height * 0.5f}; }
|
||||||
|
|
||||||
|
bool empty() const { return size.empty(); }
|
||||||
|
|
||||||
|
bool containsPoint(const Point& p) const {
|
||||||
|
return p.x >= left() && p.x <= right() &&
|
||||||
|
p.y >= top() && p.y <= bottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool contains(const Rect& r) const {
|
||||||
|
return r.left() >= left() && r.right() <= right() &&
|
||||||
|
r.top() >= top() && r.bottom() <= bottom();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool intersects(const Rect& r) const {
|
||||||
|
return !(left() > r.right() || right() < r.left() ||
|
||||||
|
top() > r.bottom() || bottom() < r.top());
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect intersection(const Rect& r) const {
|
||||||
|
float l = std::max(left(), r.left());
|
||||||
|
float t = std::max(top(), r.top());
|
||||||
|
float ri = std::min(right(), r.right());
|
||||||
|
float b = std::min(bottom(), r.bottom());
|
||||||
|
if (l < ri && t < b) return {l, t, ri - l, b - t};
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect unionWith(const Rect& r) const {
|
||||||
|
if (empty()) return r;
|
||||||
|
if (r.empty()) return *this;
|
||||||
|
float l = std::min(left(), r.left());
|
||||||
|
float t = std::min(top(), r.top());
|
||||||
|
float ri = std::max(right(), r.right());
|
||||||
|
float b = std::max(bottom(), r.bottom());
|
||||||
|
return {l, t, ri - l, b - t};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const Rect& r) const { return origin == r.origin && size == r.size; }
|
||||||
|
bool operator!=(const Rect& r) const { return !(*this == r); }
|
||||||
|
|
||||||
|
static constexpr Rect Zero() { return {0, 0, 0, 0}; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 2D 变换矩阵(基于 glm::mat4,兼容 OpenGL)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
struct Transform2D {
|
||||||
|
glm::mat4 matrix{1.0f}; // 单位矩阵
|
||||||
|
|
||||||
|
Transform2D() = default;
|
||||||
|
explicit Transform2D(const glm::mat4& m) : matrix(m) {}
|
||||||
|
|
||||||
|
static Transform2D identity() { return Transform2D{}; }
|
||||||
|
|
||||||
|
static Transform2D translation(float x, float y) {
|
||||||
|
Transform2D t;
|
||||||
|
t.matrix = glm::translate(glm::mat4(1.0f), glm::vec3(x, y, 0.0f));
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Transform2D translation(const Vec2& v) {
|
||||||
|
return translation(v.x, v.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Transform2D rotation(float degrees) {
|
||||||
|
Transform2D t;
|
||||||
|
t.matrix = glm::rotate(glm::mat4(1.0f), degrees * DEG_TO_RAD, glm::vec3(0.0f, 0.0f, 1.0f));
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Transform2D scaling(float sx, float sy) {
|
||||||
|
Transform2D t;
|
||||||
|
t.matrix = glm::scale(glm::mat4(1.0f), glm::vec3(sx, sy, 1.0f));
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Transform2D scaling(float s) {
|
||||||
|
return scaling(s, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Transform2D skewing(float skewX, float skewY) {
|
||||||
|
Transform2D t;
|
||||||
|
t.matrix = glm::mat4(1.0f);
|
||||||
|
t.matrix[1][0] = std::tan(skewX * DEG_TO_RAD);
|
||||||
|
t.matrix[0][1] = std::tan(skewY * DEG_TO_RAD);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
Transform2D operator*(const Transform2D& other) const {
|
||||||
|
return Transform2D(matrix * other.matrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
Transform2D& operator*=(const Transform2D& other) {
|
||||||
|
matrix *= other.matrix;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 transformPoint(const Vec2& p) const {
|
||||||
|
glm::vec4 result = matrix * glm::vec4(p.x, p.y, 0.0f, 1.0f);
|
||||||
|
return {result.x, result.y};
|
||||||
|
}
|
||||||
|
|
||||||
|
Transform2D inverse() const {
|
||||||
|
return Transform2D(glm::inverse(matrix));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 数学工具函数
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
namespace math {
|
||||||
|
|
||||||
|
inline float clamp(float value, float minVal, float maxVal) {
|
||||||
|
return std::clamp(value, minVal, maxVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float lerp(float a, float b, float t) {
|
||||||
|
return a + (b - a) * t;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float degrees(float radians) {
|
||||||
|
return radians * RAD_TO_DEG;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float radians(float degrees) {
|
||||||
|
return degrees * DEG_TO_RAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace math
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,507 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// String 类 - 跨平台字符串,内部统一使用 UTF-8 存储
|
||||||
|
// ============================================================================
|
||||||
|
class String {
|
||||||
|
public:
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 构造函数
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
String() = default;
|
||||||
|
|
||||||
|
String(const char* utf8) : data_(utf8 ? utf8 : "") {}
|
||||||
|
String(const std::string& utf8) : data_(utf8) {}
|
||||||
|
explicit String(const wchar_t* wide);
|
||||||
|
explicit String(const std::wstring& wide);
|
||||||
|
explicit String(const char16_t* utf16);
|
||||||
|
explicit String(const std::u16string& utf16);
|
||||||
|
explicit String(const char32_t* utf32);
|
||||||
|
explicit String(const std::u32string& utf32);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 静态工厂方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static String fromUtf8(const char* utf8);
|
||||||
|
static String fromUtf8(const std::string& utf8);
|
||||||
|
static String fromWide(const wchar_t* wide);
|
||||||
|
static String fromWide(const std::wstring& wide);
|
||||||
|
static String fromUtf16(const char16_t* utf16);
|
||||||
|
static String fromUtf16(const std::u16string& utf16);
|
||||||
|
static String fromUtf32(const char32_t* utf32);
|
||||||
|
static String fromUtf32(const std::u32string& utf32);
|
||||||
|
|
||||||
|
/// 从 GBK/GB2312 编码构造(Windows 中文系统常用)
|
||||||
|
static String fromGBK(const char* gbk);
|
||||||
|
static String fromGBK(const std::string& gbk);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 编码转换
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
const std::string& toUtf8() const { return data_; }
|
||||||
|
std::string toUtf8String() const { return data_; }
|
||||||
|
|
||||||
|
std::wstring toWide() const;
|
||||||
|
std::u16string toUtf16() const;
|
||||||
|
std::u32string toUtf32() const;
|
||||||
|
|
||||||
|
/// 转换为 GBK/GB2312 编码(Windows 中文系统常用)
|
||||||
|
std::string toGBK() const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 基础操作
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
/// 返回 Unicode 字符数(不是字节数)
|
||||||
|
size_t length() const;
|
||||||
|
|
||||||
|
/// 返回 UTF-8 字节数
|
||||||
|
size_t byteSize() const { return data_.size(); }
|
||||||
|
|
||||||
|
bool empty() const { return data_.empty(); }
|
||||||
|
|
||||||
|
const char* c_str() const { return data_.c_str(); }
|
||||||
|
const std::string& str() const { return data_; }
|
||||||
|
std::string& str() { return data_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 字符串操作
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void clear() { data_.clear(); }
|
||||||
|
|
||||||
|
String& append(const String& other);
|
||||||
|
String& append(const char* utf8);
|
||||||
|
|
||||||
|
String substring(size_t start, size_t len = npos) const;
|
||||||
|
|
||||||
|
/// 查找子串,返回 Unicode 字符索引,未找到返回 npos
|
||||||
|
size_t find(const String& substr, size_t start = 0) const;
|
||||||
|
|
||||||
|
/// 是否以指定字符串开头
|
||||||
|
bool startsWith(const String& prefix) const;
|
||||||
|
|
||||||
|
/// 是否以指定字符串结尾
|
||||||
|
bool endsWith(const String& suffix) const;
|
||||||
|
|
||||||
|
/// 去除首尾空白字符
|
||||||
|
String trim() const;
|
||||||
|
|
||||||
|
/// 分割字符串
|
||||||
|
std::vector<String> split(const String& delimiter) const;
|
||||||
|
|
||||||
|
/// 替换所有匹配子串
|
||||||
|
String replaceAll(const String& from, const String& to) const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 运算符重载
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
String operator+(const String& other) const;
|
||||||
|
String& operator+=(const String& other);
|
||||||
|
|
||||||
|
bool operator==(const String& other) const { return data_ == other.data_; }
|
||||||
|
bool operator!=(const String& other) const { return data_ != other.data_; }
|
||||||
|
bool operator<(const String& other) const { return data_ < other.data_; }
|
||||||
|
bool operator>(const String& other) const { return data_ > other.data_; }
|
||||||
|
|
||||||
|
char operator[](size_t index) const { return data_[index]; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 迭代器支持
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
auto begin() { return data_.begin(); }
|
||||||
|
auto end() { return data_.end(); }
|
||||||
|
auto begin() const { return data_.begin(); }
|
||||||
|
auto end() const { return data_.end(); }
|
||||||
|
auto cbegin() const { return data_.cbegin(); }
|
||||||
|
auto cend() const { return data_.cend(); }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 静态常量
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static constexpr size_t npos = static_cast<size_t>(-1);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 格式化字符串(类似 sprintf)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
template<typename... Args>
|
||||||
|
static String format(const char* fmt, Args&&... args);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string data_; // 内部使用 UTF-8 存储
|
||||||
|
|
||||||
|
// UTF-8 辅助函数
|
||||||
|
static size_t utf8Length(const std::string& str);
|
||||||
|
static std::string utf8Substring(const std::string& str, size_t start, size_t len);
|
||||||
|
static size_t utf8CharIndexToByteIndex(const std::string& str, size_t charIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 内联实现
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
inline String::String(const wchar_t* wide) {
|
||||||
|
if (wide) {
|
||||||
|
std::wstring wstr(wide);
|
||||||
|
*this = fromWide(wstr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline String::String(const std::wstring& wide) {
|
||||||
|
*this = fromWide(wide);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline String::String(const char16_t* utf16) {
|
||||||
|
if (utf16) {
|
||||||
|
std::u16string u16str(utf16);
|
||||||
|
*this = fromUtf16(u16str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline String::String(const std::u16string& utf16) {
|
||||||
|
*this = fromUtf16(utf16);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline String::String(const char32_t* utf32) {
|
||||||
|
if (utf32) {
|
||||||
|
std::u32string u32str(utf32);
|
||||||
|
*this = fromUtf32(u32str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline String::String(const std::u32string& utf32) {
|
||||||
|
*this = fromUtf32(utf32);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 静态工厂方法
|
||||||
|
inline String String::fromUtf8(const char* utf8) {
|
||||||
|
return String(utf8 ? utf8 : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
inline String String::fromUtf8(const std::string& utf8) {
|
||||||
|
return String(utf8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编码转换实现
|
||||||
|
inline std::wstring String::toWide() const {
|
||||||
|
if (data_.empty()) return std::wstring();
|
||||||
|
|
||||||
|
if constexpr (sizeof(wchar_t) == 4) {
|
||||||
|
// wchar_t is 32-bit (Linux/Switch): same as UTF-32
|
||||||
|
std::u32string u32 = toUtf32();
|
||||||
|
return std::wstring(u32.begin(), u32.end());
|
||||||
|
} else {
|
||||||
|
// wchar_t is 16-bit (Windows): same as UTF-16
|
||||||
|
std::u16string u16 = toUtf16();
|
||||||
|
return std::wstring(u16.begin(), u16.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline String String::fromWide(const std::wstring& wide) {
|
||||||
|
if (wide.empty()) return String();
|
||||||
|
|
||||||
|
if constexpr (sizeof(wchar_t) == 4) {
|
||||||
|
std::u32string u32(wide.begin(), wide.end());
|
||||||
|
return fromUtf32(u32);
|
||||||
|
} else {
|
||||||
|
std::u16string u16(wide.begin(), wide.end());
|
||||||
|
return fromUtf16(u16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline String String::fromWide(const wchar_t* wide) {
|
||||||
|
return wide ? fromWide(std::wstring(wide)) : String();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::u16string String::toUtf16() const {
|
||||||
|
if (data_.empty()) return std::u16string();
|
||||||
|
|
||||||
|
// UTF-8 → UTF-32 → UTF-16 (with surrogate pairs)
|
||||||
|
std::u32string u32 = toUtf32();
|
||||||
|
std::u16string result;
|
||||||
|
result.reserve(u32.size());
|
||||||
|
|
||||||
|
for (char32_t ch : u32) {
|
||||||
|
if (ch <= 0xFFFF) {
|
||||||
|
result.push_back(static_cast<char16_t>(ch));
|
||||||
|
} else if (ch <= 0x10FFFF) {
|
||||||
|
// Surrogate pair
|
||||||
|
ch -= 0x10000;
|
||||||
|
result.push_back(static_cast<char16_t>(0xD800 | (ch >> 10)));
|
||||||
|
result.push_back(static_cast<char16_t>(0xDC00 | (ch & 0x3FF)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline String String::fromUtf16(const std::u16string& utf16) {
|
||||||
|
if (utf16.empty()) return String();
|
||||||
|
|
||||||
|
// UTF-16 → UTF-32 → UTF-8
|
||||||
|
std::u32string u32;
|
||||||
|
u32.reserve(utf16.size());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < utf16.size(); ++i) {
|
||||||
|
char16_t cu = utf16[i];
|
||||||
|
char32_t ch;
|
||||||
|
if (cu >= 0xD800 && cu <= 0xDBFF && i + 1 < utf16.size()) {
|
||||||
|
// High surrogate
|
||||||
|
char16_t cl = utf16[i + 1];
|
||||||
|
if (cl >= 0xDC00 && cl <= 0xDFFF) {
|
||||||
|
ch = 0x10000 + ((static_cast<char32_t>(cu - 0xD800) << 10) |
|
||||||
|
(cl - 0xDC00));
|
||||||
|
++i;
|
||||||
|
} else {
|
||||||
|
ch = cu; // Invalid, pass through
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ch = cu;
|
||||||
|
}
|
||||||
|
u32.push_back(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fromUtf32(u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline String String::fromUtf16(const char16_t* utf16) {
|
||||||
|
return utf16 ? fromUtf16(std::u16string(utf16)) : String();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::u32string String::toUtf32() const {
|
||||||
|
std::u32string result;
|
||||||
|
result.reserve(length());
|
||||||
|
|
||||||
|
const char* ptr = data_.c_str();
|
||||||
|
const char* end = ptr + data_.size();
|
||||||
|
|
||||||
|
while (ptr < end) {
|
||||||
|
char32_t ch = 0;
|
||||||
|
unsigned char byte = static_cast<unsigned char>(*ptr);
|
||||||
|
|
||||||
|
if ((byte & 0x80) == 0) {
|
||||||
|
// 1-byte sequence
|
||||||
|
ch = byte;
|
||||||
|
ptr += 1;
|
||||||
|
} else if ((byte & 0xE0) == 0xC0) {
|
||||||
|
// 2-byte sequence
|
||||||
|
ch = (byte & 0x1F) << 6;
|
||||||
|
ch |= (static_cast<unsigned char>(ptr[1]) & 0x3F);
|
||||||
|
ptr += 2;
|
||||||
|
} else if ((byte & 0xF0) == 0xE0) {
|
||||||
|
// 3-byte sequence
|
||||||
|
ch = (byte & 0x0F) << 12;
|
||||||
|
ch |= (static_cast<unsigned char>(ptr[1]) & 0x3F) << 6;
|
||||||
|
ch |= (static_cast<unsigned char>(ptr[2]) & 0x3F);
|
||||||
|
ptr += 3;
|
||||||
|
} else if ((byte & 0xF8) == 0xF0) {
|
||||||
|
// 4-byte sequence
|
||||||
|
ch = (byte & 0x07) << 18;
|
||||||
|
ch |= (static_cast<unsigned char>(ptr[1]) & 0x3F) << 12;
|
||||||
|
ch |= (static_cast<unsigned char>(ptr[2]) & 0x3F) << 6;
|
||||||
|
ch |= (static_cast<unsigned char>(ptr[3]) & 0x3F);
|
||||||
|
ptr += 4;
|
||||||
|
} else {
|
||||||
|
// Invalid UTF-8, skip
|
||||||
|
ptr += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push_back(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline String String::fromUtf32(const std::u32string& utf32) {
|
||||||
|
std::string result;
|
||||||
|
|
||||||
|
for (char32_t ch : utf32) {
|
||||||
|
if (ch <= 0x7F) {
|
||||||
|
// 1-byte
|
||||||
|
result.push_back(static_cast<char>(ch));
|
||||||
|
} else if (ch <= 0x7FF) {
|
||||||
|
// 2-byte
|
||||||
|
result.push_back(static_cast<char>(0xC0 | ((ch >> 6) & 0x1F)));
|
||||||
|
result.push_back(static_cast<char>(0x80 | (ch & 0x3F)));
|
||||||
|
} else if (ch <= 0xFFFF) {
|
||||||
|
// 3-byte
|
||||||
|
result.push_back(static_cast<char>(0xE0 | ((ch >> 12) & 0x0F)));
|
||||||
|
result.push_back(static_cast<char>(0x80 | ((ch >> 6) & 0x3F)));
|
||||||
|
result.push_back(static_cast<char>(0x80 | (ch & 0x3F)));
|
||||||
|
} else if (ch <= 0x10FFFF) {
|
||||||
|
// 4-byte
|
||||||
|
result.push_back(static_cast<char>(0xF0 | ((ch >> 18) & 0x07)));
|
||||||
|
result.push_back(static_cast<char>(0x80 | ((ch >> 12) & 0x3F)));
|
||||||
|
result.push_back(static_cast<char>(0x80 | ((ch >> 6) & 0x3F)));
|
||||||
|
result.push_back(static_cast<char>(0x80 | (ch & 0x3F)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline String String::fromUtf32(const char32_t* utf32) {
|
||||||
|
return utf32 ? fromUtf32(std::u32string(utf32)) : String();
|
||||||
|
}
|
||||||
|
|
||||||
|
// UTF-8 辅助函数
|
||||||
|
inline size_t String::utf8Length(const std::string& str) {
|
||||||
|
size_t len = 0;
|
||||||
|
for (unsigned char c : str) {
|
||||||
|
if ((c & 0xC0) != 0x80) {
|
||||||
|
++len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t String::length() const {
|
||||||
|
return utf8Length(data_);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t String::utf8CharIndexToByteIndex(const std::string& str, size_t charIndex) {
|
||||||
|
size_t charCount = 0;
|
||||||
|
size_t byteIndex = 0;
|
||||||
|
|
||||||
|
while (byteIndex < str.size() && charCount < charIndex) {
|
||||||
|
unsigned char c = static_cast<unsigned char>(str[byteIndex]);
|
||||||
|
if ((c & 0xC0) != 0x80) {
|
||||||
|
++charCount;
|
||||||
|
}
|
||||||
|
++byteIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return byteIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string String::utf8Substring(const std::string& str, size_t start, size_t len) {
|
||||||
|
size_t startByte = utf8CharIndexToByteIndex(str, start);
|
||||||
|
size_t endByte = (len == npos) ? str.size() : utf8CharIndexToByteIndex(str, start + len);
|
||||||
|
|
||||||
|
return str.substr(startByte, endByte - startByte);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline String String::substring(size_t start, size_t len) const {
|
||||||
|
return String(utf8Substring(data_, start, len));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 字符串操作
|
||||||
|
inline String& String::append(const String& other) {
|
||||||
|
data_.append(other.data_);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline String& String::append(const char* utf8) {
|
||||||
|
if (utf8) data_.append(utf8);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t String::find(const String& substr, size_t start) const {
|
||||||
|
if (substr.empty() || start >= length()) return npos;
|
||||||
|
|
||||||
|
size_t startByte = utf8CharIndexToByteIndex(data_, start);
|
||||||
|
size_t pos = data_.find(substr.data_, startByte);
|
||||||
|
|
||||||
|
if (pos == std::string::npos) return npos;
|
||||||
|
|
||||||
|
// 转换字节索引到字符索引
|
||||||
|
return utf8Length(data_.substr(0, pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool String::startsWith(const String& prefix) const {
|
||||||
|
if (prefix.data_.size() > data_.size()) return false;
|
||||||
|
return data_.compare(0, prefix.data_.size(), prefix.data_) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool String::endsWith(const String& suffix) const {
|
||||||
|
if (suffix.data_.size() > data_.size()) return false;
|
||||||
|
return data_.compare(data_.size() - suffix.data_.size(), suffix.data_.size(), suffix.data_) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline String String::trim() const {
|
||||||
|
size_t start = 0;
|
||||||
|
while (start < data_.size() && std::isspace(static_cast<unsigned char>(data_[start]))) {
|
||||||
|
++start;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t end = data_.size();
|
||||||
|
while (end > start && std::isspace(static_cast<unsigned char>(data_[end - 1]))) {
|
||||||
|
--end;
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(data_.substr(start, end - start));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::vector<String> String::split(const String& delimiter) const {
|
||||||
|
std::vector<String> result;
|
||||||
|
|
||||||
|
if (delimiter.empty()) {
|
||||||
|
result.push_back(*this);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t start = 0;
|
||||||
|
size_t end = data_.find(delimiter.data_, start);
|
||||||
|
|
||||||
|
while (end != std::string::npos) {
|
||||||
|
result.push_back(String(data_.substr(start, end - start)));
|
||||||
|
start = end + delimiter.data_.size();
|
||||||
|
end = data_.find(delimiter.data_, start);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push_back(String(data_.substr(start)));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline String String::replaceAll(const String& from, const String& to) const {
|
||||||
|
if (from.empty()) return *this;
|
||||||
|
|
||||||
|
std::string result = data_;
|
||||||
|
size_t pos = 0;
|
||||||
|
|
||||||
|
while ((pos = result.find(from.data_, pos)) != std::string::npos) {
|
||||||
|
result.replace(pos, from.data_.size(), to.data_);
|
||||||
|
pos += to.data_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运算符重载
|
||||||
|
inline String String::operator+(const String& other) const {
|
||||||
|
String result(*this);
|
||||||
|
result.append(other);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline String& String::operator+=(const String& other) {
|
||||||
|
return append(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化字符串
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
String String::format(const char* fmt, Args&&... args) {
|
||||||
|
int size = std::snprintf(nullptr, 0, fmt, std::forward<Args>(args)...);
|
||||||
|
if (size <= 0) return String();
|
||||||
|
|
||||||
|
std::string result(size, '\0');
|
||||||
|
std::snprintf(&result[0], size + 1, fmt, std::forward<Args>(args)...);
|
||||||
|
|
||||||
|
return String(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全局运算符
|
||||||
|
inline String operator+(const char* lhs, const String& rhs) {
|
||||||
|
return String(lhs) + rhs;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 智能指针别名
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
template<typename T>
|
||||||
|
using Ptr = std::shared_ptr<T>;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
using UniquePtr = std::unique_ptr<T>;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
using WeakPtr = std::weak_ptr<T>;
|
||||||
|
|
||||||
|
/// 创建 shared_ptr 的便捷函数
|
||||||
|
template<typename T, typename... Args>
|
||||||
|
inline Ptr<T> makePtr(Args&&... args) {
|
||||||
|
return std::make_shared<T>(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 创建 unique_ptr 的便捷函数
|
||||||
|
template<typename T, typename... Args>
|
||||||
|
inline UniquePtr<T> makeUnique(Args&&... args) {
|
||||||
|
return std::make_unique<T>(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 函数别名
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
template<typename Sig>
|
||||||
|
using Function = std::function<Sig>;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 基础类型别名
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
using int8 = std::int8_t;
|
||||||
|
using int16 = std::int16_t;
|
||||||
|
using int32 = std::int32_t;
|
||||||
|
using int64 = std::int64_t;
|
||||||
|
using uint8 = std::uint8_t;
|
||||||
|
using uint16 = std::uint16_t;
|
||||||
|
using uint32 = std::uint32_t;
|
||||||
|
using uint64 = std::uint64_t;
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,97 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Easy2D v3.0 - 统一入口头文件
|
||||||
|
// 包含所有公共 API
|
||||||
|
|
||||||
|
// Core
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/core/string.h>
|
||||||
|
#include <easy2d/core/color.h>
|
||||||
|
#include <easy2d/core/math_types.h>
|
||||||
|
|
||||||
|
// Platform
|
||||||
|
#include <easy2d/platform/window.h>
|
||||||
|
#include <easy2d/platform/input.h>
|
||||||
|
|
||||||
|
// Graphics
|
||||||
|
#include <easy2d/graphics/render_backend.h>
|
||||||
|
#include <easy2d/graphics/texture.h>
|
||||||
|
#include <easy2d/graphics/font.h>
|
||||||
|
#include <easy2d/graphics/camera.h>
|
||||||
|
#include <easy2d/graphics/shader_system.h>
|
||||||
|
#include <easy2d/graphics/texture_pool.h>
|
||||||
|
#include <easy2d/graphics/render_target.h>
|
||||||
|
#include <easy2d/graphics/vram_manager.h>
|
||||||
|
|
||||||
|
// Scene
|
||||||
|
#include <easy2d/scene/node.h>
|
||||||
|
#include <easy2d/scene/scene.h>
|
||||||
|
#include <easy2d/scene/sprite.h>
|
||||||
|
#include <easy2d/scene/text.h>
|
||||||
|
#include <easy2d/scene/shape_node.h>
|
||||||
|
#include <easy2d/scene/scene_manager.h>
|
||||||
|
#include <easy2d/scene/transition.h>
|
||||||
|
|
||||||
|
// Animation
|
||||||
|
#include <easy2d/animation/sprite_frame.h>
|
||||||
|
#include <easy2d/animation/sprite_frame_cache.h>
|
||||||
|
#include <easy2d/animation/frame_property.h>
|
||||||
|
#include <easy2d/animation/animation_frame.h>
|
||||||
|
#include <easy2d/animation/animation_clip.h>
|
||||||
|
#include <easy2d/animation/animation_controller.h>
|
||||||
|
#include <easy2d/animation/animation_cache.h>
|
||||||
|
#include <easy2d/animation/interpolation_engine.h>
|
||||||
|
#include <easy2d/animation/animated_sprite.h>
|
||||||
|
#include <easy2d/animation/frame_renderer.h>
|
||||||
|
#include <easy2d/animation/animation_event.h>
|
||||||
|
#include <easy2d/animation/animation_node.h>
|
||||||
|
#include <easy2d/animation/composite_animation.h>
|
||||||
|
#include <easy2d/animation/ani_parser.h>
|
||||||
|
#include <easy2d/animation/ani_binary_parser.h>
|
||||||
|
#include <easy2d/animation/als_parser.h>
|
||||||
|
|
||||||
|
// UI
|
||||||
|
#include <easy2d/ui/widget.h>
|
||||||
|
#include <easy2d/ui/button.h>
|
||||||
|
|
||||||
|
// Action
|
||||||
|
#include <easy2d/action/action.h>
|
||||||
|
#include <easy2d/action/actions.h>
|
||||||
|
#include <easy2d/action/ease.h>
|
||||||
|
|
||||||
|
// Event
|
||||||
|
#include <easy2d/event/event.h>
|
||||||
|
#include <easy2d/event/event_queue.h>
|
||||||
|
#include <easy2d/event/event_dispatcher.h>
|
||||||
|
#include <easy2d/event/input_codes.h>
|
||||||
|
|
||||||
|
// Audio
|
||||||
|
#include <easy2d/audio/audio_engine.h>
|
||||||
|
#include <easy2d/audio/sound.h>
|
||||||
|
|
||||||
|
// Resource
|
||||||
|
#include <easy2d/resource/resource_manager.h>
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
#include <easy2d/utils/logger.h>
|
||||||
|
#include <easy2d/utils/timer.h>
|
||||||
|
#include <easy2d/utils/data.h>
|
||||||
|
#include <easy2d/utils/random.h>
|
||||||
|
|
||||||
|
// Spatial
|
||||||
|
#include <easy2d/spatial/spatial_index.h>
|
||||||
|
#include <easy2d/spatial/quadtree.h>
|
||||||
|
#include <easy2d/spatial/spatial_hash.h>
|
||||||
|
#include <easy2d/spatial/spatial_manager.h>
|
||||||
|
|
||||||
|
// Effects
|
||||||
|
#include <easy2d/effects/post_process.h>
|
||||||
|
#include <easy2d/effects/particle_system.h>
|
||||||
|
#include <easy2d/effects/custom_effect_manager.h>
|
||||||
|
|
||||||
|
// Application
|
||||||
|
#include <easy2d/app/application.h>
|
||||||
|
|
||||||
|
// Script
|
||||||
|
#include <easy2d/script/script_engine.h>
|
||||||
|
#include <easy2d/script/script_node.h>
|
||||||
|
|
@ -0,0 +1,318 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/core/color.h>
|
||||||
|
#include <easy2d/effects/particle_system.h>
|
||||||
|
#include <easy2d/effects/post_process.h>
|
||||||
|
#include <easy2d/graphics/shader_system.h>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 自定义特效类型
|
||||||
|
// ============================================================================
|
||||||
|
enum class CustomEffectType {
|
||||||
|
Particle, // 粒子特效
|
||||||
|
PostProcess, // 后处理特效
|
||||||
|
Shader, // Shader特效
|
||||||
|
Combined // 组合特效
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 自定义特效配置
|
||||||
|
// ============================================================================
|
||||||
|
struct CustomEffectConfig {
|
||||||
|
std::string name; // 特效名称
|
||||||
|
CustomEffectType type; // 特效类型
|
||||||
|
std::string description; // 描述
|
||||||
|
|
||||||
|
// 粒子特效配置
|
||||||
|
EmitterConfig emitterConfig;
|
||||||
|
|
||||||
|
// 后处理特效配置
|
||||||
|
std::string shaderVertPath; // 顶点着色器路径
|
||||||
|
std::string shaderFragPath; // 片段着色器路径
|
||||||
|
std::unordered_map<std::string, float> shaderParams; // Shader参数
|
||||||
|
|
||||||
|
// 通用配置
|
||||||
|
float duration; // 持续时间(-1表示无限)
|
||||||
|
bool loop; // 是否循环
|
||||||
|
float delay; // 延迟启动时间
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 自定义特效基类
|
||||||
|
// ============================================================================
|
||||||
|
class CustomEffect {
|
||||||
|
public:
|
||||||
|
explicit CustomEffect(const CustomEffectConfig& config);
|
||||||
|
virtual ~CustomEffect() = default;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 生命周期
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
virtual bool init();
|
||||||
|
virtual void update(float dt);
|
||||||
|
virtual void render(RenderBackend& renderer);
|
||||||
|
virtual void shutdown();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 控制
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void play();
|
||||||
|
void pause();
|
||||||
|
void stop();
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
bool isPlaying() const { return playing_; }
|
||||||
|
bool isFinished() const { return finished_; }
|
||||||
|
float getElapsedTime() const { return elapsedTime_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 配置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
const std::string& getName() const { return config_.name; }
|
||||||
|
const CustomEffectConfig& getConfig() const { return config_; }
|
||||||
|
|
||||||
|
void setPosition(const Vec2& pos) { position_ = pos; }
|
||||||
|
void setRotation(float rot) { rotation_ = rot; }
|
||||||
|
void setScale(float scale) { scale_ = scale; }
|
||||||
|
|
||||||
|
Vec2 getPosition() const { return position_; }
|
||||||
|
float getRotation() const { return rotation_; }
|
||||||
|
float getScale() const { return scale_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
CustomEffectConfig config_;
|
||||||
|
Vec2 position_ = Vec2::Zero();
|
||||||
|
float rotation_ = 0.0f;
|
||||||
|
float scale_ = 1.0f;
|
||||||
|
|
||||||
|
bool playing_ = false;
|
||||||
|
bool paused_ = false;
|
||||||
|
bool finished_ = false;
|
||||||
|
float elapsedTime_ = 0.0f;
|
||||||
|
float delayTimer_ = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 自定义粒子特效
|
||||||
|
// ============================================================================
|
||||||
|
class CustomParticleEffect : public CustomEffect {
|
||||||
|
public:
|
||||||
|
explicit CustomParticleEffect(const CustomEffectConfig& config);
|
||||||
|
|
||||||
|
bool init() override;
|
||||||
|
void update(float dt) override;
|
||||||
|
void render(RenderBackend& renderer) override;
|
||||||
|
void shutdown() override;
|
||||||
|
|
||||||
|
void play();
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
Ptr<ParticleEmitter> getEmitter() { return emitter_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ptr<ParticleSystem> particleSystem_;
|
||||||
|
Ptr<ParticleEmitter> emitter_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 自定义后处理特效
|
||||||
|
// ============================================================================
|
||||||
|
class CustomPostProcessEffect : public CustomEffect, public PostProcessEffect {
|
||||||
|
public:
|
||||||
|
explicit CustomPostProcessEffect(const CustomEffectConfig& config);
|
||||||
|
|
||||||
|
bool init() override;
|
||||||
|
void update(float dt) override;
|
||||||
|
void shutdown() override;
|
||||||
|
|
||||||
|
void onShaderBind(GLShader& shader) override;
|
||||||
|
|
||||||
|
void setParam(const std::string& name, float value);
|
||||||
|
float getParam(const std::string& name) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<std::string, float> runtimeParams_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 自定义特效工厂
|
||||||
|
// ============================================================================
|
||||||
|
class CustomEffectFactory {
|
||||||
|
public:
|
||||||
|
using EffectCreator = std::function<Ptr<CustomEffect>(const CustomEffectConfig&)>;
|
||||||
|
|
||||||
|
static CustomEffectFactory& getInstance();
|
||||||
|
|
||||||
|
// 注册自定义特效创建器
|
||||||
|
void registerEffect(const std::string& typeName, EffectCreator creator);
|
||||||
|
|
||||||
|
// 创建特效
|
||||||
|
Ptr<CustomEffect> create(const std::string& typeName, const CustomEffectConfig& config);
|
||||||
|
|
||||||
|
// 检查是否已注册
|
||||||
|
bool isRegistered(const std::string& typeName) const;
|
||||||
|
|
||||||
|
// 获取所有已注册的类型
|
||||||
|
std::vector<std::string> getRegisteredTypes() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
CustomEffectFactory() = default;
|
||||||
|
~CustomEffectFactory() = default;
|
||||||
|
CustomEffectFactory(const CustomEffectFactory&) = delete;
|
||||||
|
CustomEffectFactory& operator=(const CustomEffectFactory&) = delete;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, EffectCreator> creators_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 自定义特效管理器
|
||||||
|
// ============================================================================
|
||||||
|
class CustomEffectManager {
|
||||||
|
public:
|
||||||
|
static CustomEffectManager& getInstance();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 初始化和关闭
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool init();
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 特效管理
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从文件加载特效配置(支持JSON和文本格式)
|
||||||
|
* 自动检测格式:JSON格式优先,失败则回退到文本格式
|
||||||
|
*/
|
||||||
|
bool loadFromFile(const std::string& filepath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从文本文件加载特效配置(简化格式)
|
||||||
|
* 格式:每行一个参数,如 EMISSION 100
|
||||||
|
*/
|
||||||
|
bool loadFromTextFile(const std::string& filepath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 保存特效配置到文件
|
||||||
|
* @param useJson true=JSON格式, false=文本格式
|
||||||
|
*/
|
||||||
|
bool saveToFile(const std::string& name, const std::string& filepath, bool useJson = true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 保存所有特效配置到一个JSON文件
|
||||||
|
*/
|
||||||
|
bool saveAllToFile(const std::string& filepath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 注册特效配置
|
||||||
|
*/
|
||||||
|
void registerConfig(const std::string& name, const CustomEffectConfig& config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取特效配置
|
||||||
|
*/
|
||||||
|
CustomEffectConfig* getConfig(const std::string& name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 移除特效配置
|
||||||
|
*/
|
||||||
|
void removeConfig(const std::string& name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取所有配置名称
|
||||||
|
*/
|
||||||
|
std::vector<std::string> getConfigNames() const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 特效实例管理
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建特效实例
|
||||||
|
*/
|
||||||
|
Ptr<CustomEffect> createEffect(const std::string& name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从配置直接创建特效
|
||||||
|
*/
|
||||||
|
Ptr<CustomEffect> createEffectFromConfig(const CustomEffectConfig& config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 销毁特效实例
|
||||||
|
*/
|
||||||
|
void destroyEffect(Ptr<CustomEffect> effect);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 更新所有特效
|
||||||
|
*/
|
||||||
|
void update(float dt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染所有特效
|
||||||
|
*/
|
||||||
|
void render(RenderBackend& renderer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 停止所有特效
|
||||||
|
*/
|
||||||
|
void stopAll();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 便捷方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 播放特效(简写)
|
||||||
|
*/
|
||||||
|
Ptr<CustomEffect> play(const std::string& name, const Vec2& position);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 播放特效并自动销毁
|
||||||
|
*/
|
||||||
|
void playOneShot(const std::string& name, const Vec2& position);
|
||||||
|
|
||||||
|
private:
|
||||||
|
CustomEffectManager() = default;
|
||||||
|
~CustomEffectManager() = default;
|
||||||
|
CustomEffectManager(const CustomEffectManager&) = delete;
|
||||||
|
CustomEffectManager& operator=(const CustomEffectManager&) = delete;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, CustomEffectConfig> configs_;
|
||||||
|
std::vector<Ptr<CustomEffect>> activeEffects_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 便捷宏
|
||||||
|
// ============================================================================
|
||||||
|
#define E2D_CUSTOM_EFFECT_MANAGER() ::easy2d::CustomEffectManager::getInstance()
|
||||||
|
#define E2D_CUSTOM_EFFECT_FACTORY() ::easy2d::CustomEffectFactory::getInstance()
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 预设特效快速创建
|
||||||
|
// ============================================================================
|
||||||
|
class EffectBuilder {
|
||||||
|
public:
|
||||||
|
// 粒子特效
|
||||||
|
static CustomEffectConfig Particle(const std::string& name);
|
||||||
|
static CustomEffectConfig Fire(const std::string& name);
|
||||||
|
static CustomEffectConfig Smoke(const std::string& name);
|
||||||
|
static CustomEffectConfig Explosion(const std::string& name);
|
||||||
|
static CustomEffectConfig Magic(const std::string& name);
|
||||||
|
static CustomEffectConfig Sparkle(const std::string& name);
|
||||||
|
|
||||||
|
// 后处理特效
|
||||||
|
static CustomEffectConfig Bloom(const std::string& name);
|
||||||
|
static CustomEffectConfig Blur(const std::string& name);
|
||||||
|
static CustomEffectConfig Vignette(const std::string& name);
|
||||||
|
static CustomEffectConfig ColorGrading(const std::string& name);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,244 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/core/color.h>
|
||||||
|
#include <easy2d/scene/node.h>
|
||||||
|
#include <easy2d/graphics/texture.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 粒子数据
|
||||||
|
// ============================================================================
|
||||||
|
struct Particle {
|
||||||
|
Vec2 position;
|
||||||
|
Vec2 velocity;
|
||||||
|
Vec2 acceleration;
|
||||||
|
float rotation;
|
||||||
|
float angularVelocity;
|
||||||
|
float size;
|
||||||
|
float sizeDelta;
|
||||||
|
Color color;
|
||||||
|
Color colorDelta;
|
||||||
|
float life;
|
||||||
|
float maxLife;
|
||||||
|
bool active;
|
||||||
|
|
||||||
|
Particle() : position(Vec2::Zero()), velocity(Vec2::Zero()),
|
||||||
|
acceleration(Vec2::Zero()), rotation(0.0f),
|
||||||
|
angularVelocity(0.0f), size(1.0f), sizeDelta(0.0f),
|
||||||
|
color(Colors::White), colorDelta(Colors::Transparent),
|
||||||
|
life(0.0f), maxLife(1.0f), active(false) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 发射器配置
|
||||||
|
// ============================================================================
|
||||||
|
struct EmitterConfig {
|
||||||
|
// 发射速率
|
||||||
|
float emissionRate = 100.0f; // 每秒发射粒子数
|
||||||
|
float emissionDuration = -1.0f; // 发射持续时间(-1表示无限)
|
||||||
|
|
||||||
|
// 粒子生命周期
|
||||||
|
float minLife = 1.0f;
|
||||||
|
float maxLife = 2.0f;
|
||||||
|
|
||||||
|
// 粒子大小
|
||||||
|
float minStartSize = 10.0f;
|
||||||
|
float maxStartSize = 20.0f;
|
||||||
|
float minEndSize = 0.0f;
|
||||||
|
float maxEndSize = 5.0f;
|
||||||
|
|
||||||
|
// 粒子速度
|
||||||
|
Vec2 minVelocity = Vec2(-50.0f, -50.0f);
|
||||||
|
Vec2 maxVelocity = Vec2(50.0f, 50.0f);
|
||||||
|
|
||||||
|
// 粒子加速度
|
||||||
|
Vec2 acceleration = Vec2(0.0f, -100.0f); // 重力
|
||||||
|
|
||||||
|
// 粒子旋转
|
||||||
|
float minRotation = 0.0f;
|
||||||
|
float maxRotation = 360.0f;
|
||||||
|
float minAngularVelocity = -90.0f;
|
||||||
|
float maxAngularVelocity = 90.0f;
|
||||||
|
|
||||||
|
// 颜色
|
||||||
|
Color startColor = Colors::White;
|
||||||
|
Color endColor = Colors::Transparent;
|
||||||
|
|
||||||
|
// 发射形状
|
||||||
|
enum class Shape {
|
||||||
|
Point, // 点发射
|
||||||
|
Circle, // 圆形区域
|
||||||
|
Rectangle, // 矩形区域
|
||||||
|
Cone // 锥形
|
||||||
|
};
|
||||||
|
Shape shape = Shape::Point;
|
||||||
|
float shapeRadius = 50.0f; // 圆形/锥形半径
|
||||||
|
Vec2 shapeSize = Vec2(100.0f, 100.0f); // 矩形大小
|
||||||
|
float coneAngle = 45.0f; // 锥形角度
|
||||||
|
|
||||||
|
// 纹理
|
||||||
|
Ptr<Texture> texture = nullptr;
|
||||||
|
|
||||||
|
// 混合模式
|
||||||
|
BlendMode blendMode = BlendMode::Additive;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 粒子发射器
|
||||||
|
// ============================================================================
|
||||||
|
class ParticleEmitter {
|
||||||
|
public:
|
||||||
|
ParticleEmitter();
|
||||||
|
~ParticleEmitter() = default;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 初始化和关闭
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool init(size_t maxParticles);
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 配置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setConfig(const EmitterConfig& config) { config_ = config; }
|
||||||
|
const EmitterConfig& getConfig() const { return config_; }
|
||||||
|
|
||||||
|
// 链式配置API
|
||||||
|
ParticleEmitter& withEmissionRate(float rate) { config_.emissionRate = rate; return *this; }
|
||||||
|
ParticleEmitter& withLife(float minLife, float maxLife) {
|
||||||
|
config_.minLife = minLife; config_.maxLife = maxLife; return *this;
|
||||||
|
}
|
||||||
|
ParticleEmitter& withSize(float minStart, float maxStart, float minEnd = 0.0f, float maxEnd = 0.0f) {
|
||||||
|
config_.minStartSize = minStart; config_.maxStartSize = maxStart;
|
||||||
|
config_.minEndSize = minEnd; config_.maxEndSize = maxEnd;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ParticleEmitter& withVelocity(const Vec2& minVel, const Vec2& maxVel) {
|
||||||
|
config_.minVelocity = minVel; config_.maxVelocity = maxVel; return *this;
|
||||||
|
}
|
||||||
|
ParticleEmitter& withAcceleration(const Vec2& accel) { config_.acceleration = accel; return *this; }
|
||||||
|
ParticleEmitter& withColor(const Color& start, const Color& end) {
|
||||||
|
config_.startColor = start; config_.endColor = end; return *this;
|
||||||
|
}
|
||||||
|
ParticleEmitter& withTexture(Ptr<Texture> texture) { config_.texture = texture; return *this; }
|
||||||
|
ParticleEmitter& withBlendMode(BlendMode mode) { config_.blendMode = mode; return *this; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 发射控制
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void start();
|
||||||
|
void stop();
|
||||||
|
void burst(int count); // 爆发发射
|
||||||
|
void reset();
|
||||||
|
bool isEmitting() const { return emitting_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 更新和渲染
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void update(float dt);
|
||||||
|
void render(RenderBackend& renderer);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 状态查询
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
size_t getActiveParticleCount() const { return activeCount_; }
|
||||||
|
size_t getMaxParticles() const { return particles_.size(); }
|
||||||
|
bool isActive() const { return activeCount_ > 0 || emitting_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 变换
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setPosition(const Vec2& pos) { position_ = pos; }
|
||||||
|
void setRotation(float rot) { rotation_ = rot; }
|
||||||
|
Vec2 getPosition() const { return position_; }
|
||||||
|
float getRotation() const { return rotation_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
EmitterConfig config_;
|
||||||
|
std::vector<Particle> particles_;
|
||||||
|
size_t activeCount_ = 0;
|
||||||
|
|
||||||
|
Vec2 position_ = Vec2::Zero();
|
||||||
|
float rotation_ = 0.0f;
|
||||||
|
|
||||||
|
bool emitting_ = false;
|
||||||
|
float emissionTimer_ = 0.0f;
|
||||||
|
float emissionTime_ = 0.0f;
|
||||||
|
|
||||||
|
std::mt19937 rng_;
|
||||||
|
|
||||||
|
void emitParticle();
|
||||||
|
float randomFloat(float min, float max);
|
||||||
|
Vec2 randomPointInShape();
|
||||||
|
Vec2 randomVelocity();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 粒子系统 - 管理多个发射器
|
||||||
|
// ============================================================================
|
||||||
|
class ParticleSystem : public Node {
|
||||||
|
public:
|
||||||
|
ParticleSystem();
|
||||||
|
~ParticleSystem() override = default;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 静态创建方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static Ptr<ParticleSystem> create();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 发射器管理
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
Ptr<ParticleEmitter> addEmitter(const EmitterConfig& config = {});
|
||||||
|
void removeEmitter(Ptr<ParticleEmitter> emitter);
|
||||||
|
void removeAllEmitters();
|
||||||
|
size_t getEmitterCount() const { return emitters_.size(); }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 全局控制
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void startAll();
|
||||||
|
void stopAll();
|
||||||
|
void resetAll();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 预设
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static EmitterConfig PresetFire();
|
||||||
|
static EmitterConfig PresetSmoke();
|
||||||
|
static EmitterConfig PresetExplosion();
|
||||||
|
static EmitterConfig PresetSparkle();
|
||||||
|
static EmitterConfig PresetRain();
|
||||||
|
static EmitterConfig PresetSnow();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 重写Node方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void onUpdate(float dt) override;
|
||||||
|
void onDraw(RenderBackend& renderer) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Ptr<ParticleEmitter>> emitters_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 粒子预设(便捷类)
|
||||||
|
// ============================================================================
|
||||||
|
class ParticlePreset {
|
||||||
|
public:
|
||||||
|
static EmitterConfig Fire();
|
||||||
|
static EmitterConfig Smoke();
|
||||||
|
static EmitterConfig Explosion();
|
||||||
|
static EmitterConfig Sparkle();
|
||||||
|
static EmitterConfig Rain();
|
||||||
|
static EmitterConfig Snow();
|
||||||
|
static EmitterConfig Magic();
|
||||||
|
static EmitterConfig Bubbles();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,225 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/core/color.h>
|
||||||
|
#include <easy2d/graphics/texture.h>
|
||||||
|
#include <easy2d/graphics/opengl/gl_shader.h>
|
||||||
|
#include <string>
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 前向声明
|
||||||
|
// ============================================================================
|
||||||
|
class RenderTarget;
|
||||||
|
class RenderBackend;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 后处理效果基类
|
||||||
|
// ============================================================================
|
||||||
|
class PostProcessEffect {
|
||||||
|
public:
|
||||||
|
PostProcessEffect(const std::string& name);
|
||||||
|
virtual ~PostProcessEffect() = default;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 生命周期
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化效果
|
||||||
|
*/
|
||||||
|
virtual bool init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭效果
|
||||||
|
*/
|
||||||
|
virtual void shutdown();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 渲染
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 应用效果
|
||||||
|
* @param source 输入纹理
|
||||||
|
* @param target 输出渲染目标
|
||||||
|
* @param renderer 渲染后端
|
||||||
|
*/
|
||||||
|
virtual void apply(const Texture& source, RenderTarget& target, RenderBackend& renderer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置Shader参数(子类重写)
|
||||||
|
*/
|
||||||
|
virtual void onShaderBind(GLShader& shader) {}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 状态
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
const std::string& getName() const { return name_; }
|
||||||
|
bool isEnabled() const { return enabled_; }
|
||||||
|
void setEnabled(bool enabled) { enabled_ = enabled; }
|
||||||
|
bool isValid() const { return valid_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 链式API
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
PostProcessEffect& withEnabled(bool enabled) {
|
||||||
|
enabled_ = enabled;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::string name_;
|
||||||
|
bool enabled_ = true;
|
||||||
|
bool valid_ = false;
|
||||||
|
Ptr<GLShader> shader_;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 加载自定义Shader
|
||||||
|
*/
|
||||||
|
bool loadShader(const std::string& vertSource, const std::string& fragSource);
|
||||||
|
bool loadShaderFromFile(const std::string& vertPath, const std::string& fragPath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染全屏四边形
|
||||||
|
*/
|
||||||
|
void renderFullscreenQuad();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static GLuint quadVao_;
|
||||||
|
static GLuint quadVbo_;
|
||||||
|
static bool quadInitialized_;
|
||||||
|
|
||||||
|
void initQuad();
|
||||||
|
void destroyQuad();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 后处理栈 - 管理多个后处理效果
|
||||||
|
// ============================================================================
|
||||||
|
class PostProcessStack {
|
||||||
|
public:
|
||||||
|
PostProcessStack();
|
||||||
|
~PostProcessStack();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 初始化和关闭
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool init(int width, int height);
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 效果管理
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 添加效果到栈
|
||||||
|
*/
|
||||||
|
void addEffect(Ptr<PostProcessEffect> effect);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 插入效果到指定位置
|
||||||
|
*/
|
||||||
|
void insertEffect(size_t index, Ptr<PostProcessEffect> effect);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 移除效果
|
||||||
|
*/
|
||||||
|
void removeEffect(const std::string& name);
|
||||||
|
void removeEffect(size_t index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取效果
|
||||||
|
*/
|
||||||
|
Ptr<PostProcessEffect> getEffect(const std::string& name);
|
||||||
|
Ptr<PostProcessEffect> getEffect(size_t index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清空所有效果
|
||||||
|
*/
|
||||||
|
void clearEffects();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取效果数量
|
||||||
|
*/
|
||||||
|
size_t getEffectCount() const { return effects_.size(); }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 渲染
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 开始捕获场景
|
||||||
|
*/
|
||||||
|
void beginCapture();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 结束捕获并应用所有效果
|
||||||
|
*/
|
||||||
|
void endCapture(RenderBackend& renderer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 直接应用效果到纹理
|
||||||
|
*/
|
||||||
|
void process(const Texture& source, RenderTarget& target, RenderBackend& renderer);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 配置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void resize(int width, int height);
|
||||||
|
bool isValid() const { return valid_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 便捷方法 - 添加内置效果
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
PostProcessStack& addBloom(float intensity = 1.0f, float threshold = 0.8f);
|
||||||
|
PostProcessStack& addBlur(float radius = 2.0f);
|
||||||
|
PostProcessStack& addColorGrading(const Color& tint);
|
||||||
|
PostProcessStack& addVignette(float intensity = 0.5f);
|
||||||
|
PostProcessStack& addChromaticAberration(float amount = 1.0f);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Ptr<PostProcessEffect>> effects_;
|
||||||
|
Ptr<RenderTarget> renderTargetA_;
|
||||||
|
Ptr<RenderTarget> renderTargetB_;
|
||||||
|
int width_ = 0;
|
||||||
|
int height_ = 0;
|
||||||
|
bool valid_ = false;
|
||||||
|
bool capturing_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 全局后处理管理
|
||||||
|
// ============================================================================
|
||||||
|
class PostProcessManager {
|
||||||
|
public:
|
||||||
|
static PostProcessManager& getInstance();
|
||||||
|
|
||||||
|
void init(int width, int height);
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
PostProcessStack& getMainStack() { return mainStack_; }
|
||||||
|
|
||||||
|
void resize(int width, int height);
|
||||||
|
void beginFrame();
|
||||||
|
void endFrame(RenderBackend& renderer);
|
||||||
|
|
||||||
|
private:
|
||||||
|
PostProcessManager() = default;
|
||||||
|
~PostProcessManager() = default;
|
||||||
|
PostProcessManager(const PostProcessManager&) = delete;
|
||||||
|
PostProcessManager& operator=(const PostProcessManager&) = delete;
|
||||||
|
|
||||||
|
PostProcessStack mainStack_;
|
||||||
|
bool initialized_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 便捷宏
|
||||||
|
// ============================================================================
|
||||||
|
#define E2D_POST_PROCESS() ::easy2d::PostProcessManager::getInstance()
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,185 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/core/math_types.h>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 事件类型枚举
|
||||||
|
// ============================================================================
|
||||||
|
enum class EventType {
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
// 窗口事件
|
||||||
|
WindowClose,
|
||||||
|
WindowResize,
|
||||||
|
WindowFocus,
|
||||||
|
WindowLostFocus,
|
||||||
|
WindowMoved,
|
||||||
|
|
||||||
|
// 键盘事件
|
||||||
|
KeyPressed,
|
||||||
|
KeyReleased,
|
||||||
|
KeyRepeat,
|
||||||
|
|
||||||
|
// 鼠标事件
|
||||||
|
MouseButtonPressed,
|
||||||
|
MouseButtonReleased,
|
||||||
|
MouseMoved,
|
||||||
|
MouseScrolled,
|
||||||
|
|
||||||
|
// UI 事件
|
||||||
|
UIHoverEnter,
|
||||||
|
UIHoverExit,
|
||||||
|
UIPressed,
|
||||||
|
UIReleased,
|
||||||
|
UIClicked,
|
||||||
|
|
||||||
|
// 游戏手柄事件
|
||||||
|
GamepadConnected,
|
||||||
|
GamepadDisconnected,
|
||||||
|
GamepadButtonPressed,
|
||||||
|
GamepadButtonReleased,
|
||||||
|
GamepadAxisMoved,
|
||||||
|
|
||||||
|
// 触摸事件 (移动端)
|
||||||
|
TouchBegan,
|
||||||
|
TouchMoved,
|
||||||
|
TouchEnded,
|
||||||
|
TouchCancelled,
|
||||||
|
|
||||||
|
// 自定义事件
|
||||||
|
Custom
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 键盘事件数据
|
||||||
|
// ============================================================================
|
||||||
|
struct KeyEvent {
|
||||||
|
int keyCode;
|
||||||
|
int scancode;
|
||||||
|
int mods; // 修饰键 (Shift, Ctrl, Alt, etc.)
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 鼠标事件数据
|
||||||
|
// ============================================================================
|
||||||
|
struct MouseButtonEvent {
|
||||||
|
int button;
|
||||||
|
int mods;
|
||||||
|
Vec2 position;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MouseMoveEvent {
|
||||||
|
Vec2 position;
|
||||||
|
Vec2 delta;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MouseScrollEvent {
|
||||||
|
Vec2 offset;
|
||||||
|
Vec2 position;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 窗口事件数据
|
||||||
|
// ============================================================================
|
||||||
|
struct WindowResizeEvent {
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WindowMoveEvent {
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 游戏手柄事件数据
|
||||||
|
// ============================================================================
|
||||||
|
struct GamepadButtonEvent {
|
||||||
|
int gamepadId;
|
||||||
|
int button;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GamepadAxisEvent {
|
||||||
|
int gamepadId;
|
||||||
|
int axis;
|
||||||
|
float value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 触摸事件数据
|
||||||
|
// ============================================================================
|
||||||
|
struct TouchEvent {
|
||||||
|
int touchId;
|
||||||
|
Vec2 position;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 自定义事件数据
|
||||||
|
// ============================================================================
|
||||||
|
struct CustomEvent {
|
||||||
|
uint32_t id;
|
||||||
|
void* data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 事件结构
|
||||||
|
// ============================================================================
|
||||||
|
struct Event {
|
||||||
|
EventType type = EventType::None;
|
||||||
|
double timestamp = 0.0;
|
||||||
|
bool handled = false;
|
||||||
|
|
||||||
|
// 事件数据联合体
|
||||||
|
std::variant<
|
||||||
|
std::monostate,
|
||||||
|
KeyEvent,
|
||||||
|
MouseButtonEvent,
|
||||||
|
MouseMoveEvent,
|
||||||
|
MouseScrollEvent,
|
||||||
|
WindowResizeEvent,
|
||||||
|
WindowMoveEvent,
|
||||||
|
GamepadButtonEvent,
|
||||||
|
GamepadAxisEvent,
|
||||||
|
TouchEvent,
|
||||||
|
CustomEvent
|
||||||
|
> data;
|
||||||
|
|
||||||
|
// 便捷访问方法
|
||||||
|
bool isWindowEvent() const {
|
||||||
|
return type == EventType::WindowClose ||
|
||||||
|
type == EventType::WindowResize ||
|
||||||
|
type == EventType::WindowFocus ||
|
||||||
|
type == EventType::WindowLostFocus ||
|
||||||
|
type == EventType::WindowMoved;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isKeyboardEvent() const {
|
||||||
|
return type == EventType::KeyPressed ||
|
||||||
|
type == EventType::KeyReleased ||
|
||||||
|
type == EventType::KeyRepeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isMouseEvent() const {
|
||||||
|
return type == EventType::MouseButtonPressed ||
|
||||||
|
type == EventType::MouseButtonReleased ||
|
||||||
|
type == EventType::MouseMoved ||
|
||||||
|
type == EventType::MouseScrolled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 静态工厂方法
|
||||||
|
static Event createWindowResize(int width, int height);
|
||||||
|
static Event createWindowClose();
|
||||||
|
static Event createKeyPress(int keyCode, int scancode, int mods);
|
||||||
|
static Event createKeyRelease(int keyCode, int scancode, int mods);
|
||||||
|
static Event createMouseButtonPress(int button, int mods, const Vec2& pos);
|
||||||
|
static Event createMouseButtonRelease(int button, int mods, const Vec2& pos);
|
||||||
|
static Event createMouseMove(const Vec2& pos, const Vec2& delta);
|
||||||
|
static Event createMouseScroll(const Vec2& offset, const Vec2& pos);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/event/event.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 事件监听器 ID
|
||||||
|
// ============================================================================
|
||||||
|
using ListenerId = uint64_t;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 事件分发器
|
||||||
|
// ============================================================================
|
||||||
|
class EventDispatcher {
|
||||||
|
public:
|
||||||
|
using EventCallback = std::function<void(Event&)>;
|
||||||
|
|
||||||
|
EventDispatcher();
|
||||||
|
~EventDispatcher() = default;
|
||||||
|
|
||||||
|
// 添加监听器
|
||||||
|
ListenerId addListener(EventType type, EventCallback callback);
|
||||||
|
|
||||||
|
// 移除监听器
|
||||||
|
void removeListener(ListenerId id);
|
||||||
|
void removeAllListeners(EventType type);
|
||||||
|
void removeAllListeners();
|
||||||
|
|
||||||
|
// 分发事件
|
||||||
|
void dispatch(Event& event);
|
||||||
|
void dispatch(const Event& event);
|
||||||
|
|
||||||
|
// 处理事件队列
|
||||||
|
void processQueue(class EventQueue& queue);
|
||||||
|
|
||||||
|
// 统计
|
||||||
|
size_t getListenerCount(EventType type) const;
|
||||||
|
size_t getTotalListenerCount() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Listener {
|
||||||
|
ListenerId id;
|
||||||
|
EventType type;
|
||||||
|
EventCallback callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unordered_map<EventType, std::vector<Listener>> listeners_;
|
||||||
|
ListenerId nextId_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/event/event.h>
|
||||||
|
#include <queue>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 事件队列 - 线程安全的事件队列
|
||||||
|
// ============================================================================
|
||||||
|
class EventQueue {
|
||||||
|
public:
|
||||||
|
EventQueue();
|
||||||
|
~EventQueue() = default;
|
||||||
|
|
||||||
|
// 添加事件到队列
|
||||||
|
void push(const Event& event);
|
||||||
|
void push(Event&& event);
|
||||||
|
|
||||||
|
// 从队列取出事件
|
||||||
|
bool poll(Event& event);
|
||||||
|
|
||||||
|
// 查看队列头部事件(不移除)
|
||||||
|
bool peek(Event& event) const;
|
||||||
|
|
||||||
|
// 清空队列
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
// 队列状态
|
||||||
|
bool empty() const;
|
||||||
|
size_t size() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::queue<Event> queue_;
|
||||||
|
mutable std::mutex mutex_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -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
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/core/math_types.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Alpha 遮罩 - 存储图片的非透明区域信息
|
||||||
|
// ============================================================================
|
||||||
|
class AlphaMask {
|
||||||
|
public:
|
||||||
|
AlphaMask() = default;
|
||||||
|
AlphaMask(int width, int height);
|
||||||
|
|
||||||
|
/// 从像素数据创建遮罩
|
||||||
|
static AlphaMask createFromPixels(const uint8_t* pixels, int width, int height, int channels);
|
||||||
|
|
||||||
|
/// 获取指定位置的透明度(0-255)
|
||||||
|
uint8_t getAlpha(int x, int y) const;
|
||||||
|
|
||||||
|
/// 检查指定位置是否不透明
|
||||||
|
bool isOpaque(int x, int y, uint8_t threshold = 128) const;
|
||||||
|
|
||||||
|
/// 检查指定位置是否在遮罩范围内
|
||||||
|
bool isValid(int x, int y) const;
|
||||||
|
|
||||||
|
/// 获取遮罩尺寸
|
||||||
|
int getWidth() const { return width_; }
|
||||||
|
int getHeight() const { return height_; }
|
||||||
|
Size getSize() const { return Size(static_cast<float>(width_), static_cast<float>(height_)); }
|
||||||
|
|
||||||
|
/// 获取原始数据
|
||||||
|
const std::vector<uint8_t>& getData() const { return data_; }
|
||||||
|
|
||||||
|
/// 检查遮罩是否有效
|
||||||
|
bool isValid() const { return !data_.empty() && width_ > 0 && height_ > 0; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
int width_ = 0;
|
||||||
|
int height_ = 0;
|
||||||
|
std::vector<uint8_t> data_; // Alpha值数组
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/core/math_types.h>
|
||||||
|
#include <easy2d/core/color.h>
|
||||||
|
#include <glm/mat4x4.hpp>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 2D 正交相机
|
||||||
|
// ============================================================================
|
||||||
|
class Camera {
|
||||||
|
public:
|
||||||
|
Camera();
|
||||||
|
Camera(float left, float right, float bottom, float top);
|
||||||
|
Camera(const Size& viewport);
|
||||||
|
~Camera() = default;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 位置和变换
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setPosition(const Vec2& position);
|
||||||
|
void setPosition(float x, float y);
|
||||||
|
Vec2 getPosition() const { return position_; }
|
||||||
|
|
||||||
|
void setRotation(float degrees);
|
||||||
|
float getRotation() const { return rotation_; }
|
||||||
|
|
||||||
|
void setZoom(float zoom);
|
||||||
|
float getZoom() const { return zoom_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 视口设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setViewport(float left, float right, float bottom, float top);
|
||||||
|
void setViewport(const Rect& rect);
|
||||||
|
Rect getViewport() const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 矩阵获取
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
glm::mat4 getViewMatrix() const;
|
||||||
|
glm::mat4 getProjectionMatrix() const;
|
||||||
|
glm::mat4 getViewProjectionMatrix() const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 坐标转换
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
Vec2 screenToWorld(const Vec2& screenPos) const;
|
||||||
|
Vec2 worldToScreen(const Vec2& worldPos) const;
|
||||||
|
Vec2 screenToWorld(float x, float y) const;
|
||||||
|
Vec2 worldToScreen(float x, float y) const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 移动相机
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void move(const Vec2& offset);
|
||||||
|
void move(float x, float y);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 边界限制
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setBounds(const Rect& bounds);
|
||||||
|
void clearBounds();
|
||||||
|
void clampToBounds();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 快捷方法:看向某点
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void lookAt(const Vec2& target);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Vec2 position_ = Vec2::Zero();
|
||||||
|
float rotation_ = 0.0f;
|
||||||
|
float zoom_ = 1.0f;
|
||||||
|
|
||||||
|
float left_ = -1.0f;
|
||||||
|
float right_ = 1.0f;
|
||||||
|
float bottom_ = -1.0f;
|
||||||
|
float top_ = 1.0f;
|
||||||
|
|
||||||
|
Rect bounds_;
|
||||||
|
bool hasBounds_ = false;
|
||||||
|
|
||||||
|
mutable glm::mat4 viewMatrix_;
|
||||||
|
mutable glm::mat4 projMatrix_;
|
||||||
|
mutable glm::mat4 vpMatrix_;
|
||||||
|
mutable bool viewDirty_ = true;
|
||||||
|
mutable bool projDirty_ = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/core/color.h>
|
||||||
|
#include <easy2d/core/string.h>
|
||||||
|
#include <easy2d/core/math_types.h>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 字形信息
|
||||||
|
// ============================================================================
|
||||||
|
struct Glyph {
|
||||||
|
float u0, v0; // 纹理坐标左下角
|
||||||
|
float u1, v1; // 纹理坐标右上角
|
||||||
|
float width; // 字形宽度(像素)
|
||||||
|
float height; // 字形高度(像素)
|
||||||
|
float bearingX; // 水平偏移
|
||||||
|
float bearingY; // 垂直偏移
|
||||||
|
float advance; // 前进距离
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 字体图集接口
|
||||||
|
// ============================================================================
|
||||||
|
class FontAtlas {
|
||||||
|
public:
|
||||||
|
virtual ~FontAtlas() = default;
|
||||||
|
|
||||||
|
// 获取字形信息
|
||||||
|
virtual const Glyph* getGlyph(char32_t codepoint) const = 0;
|
||||||
|
|
||||||
|
// 获取纹理
|
||||||
|
virtual class Texture* getTexture() const = 0;
|
||||||
|
|
||||||
|
// 获取字体大小
|
||||||
|
virtual int getFontSize() const = 0;
|
||||||
|
|
||||||
|
virtual float getAscent() const = 0;
|
||||||
|
virtual float getDescent() const = 0;
|
||||||
|
virtual float getLineGap() const = 0;
|
||||||
|
virtual float getLineHeight() const = 0;
|
||||||
|
|
||||||
|
// 计算文字尺寸
|
||||||
|
virtual Vec2 measureText(const String& text) = 0;
|
||||||
|
|
||||||
|
// 是否支持 SDF 渲染
|
||||||
|
virtual bool isSDF() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/core/color.h>
|
||||||
|
#include <easy2d/core/math_types.h>
|
||||||
|
#include <easy2d/graphics/font.h>
|
||||||
|
#include <easy2d/graphics/texture.h>
|
||||||
|
#include <easy2d/graphics/opengl/gl_texture.h>
|
||||||
|
#include <stb/stb_truetype.h>
|
||||||
|
#include <stb/stb_rect_pack.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// OpenGL 字体图集实现 - 使用 stb_rect_pack 进行矩形打包
|
||||||
|
// ============================================================================
|
||||||
|
class GLFontAtlas : public FontAtlas {
|
||||||
|
public:
|
||||||
|
GLFontAtlas(const std::string& filepath, int fontSize, bool useSDF = false);
|
||||||
|
~GLFontAtlas();
|
||||||
|
|
||||||
|
// FontAtlas 接口实现
|
||||||
|
const Glyph* getGlyph(char32_t codepoint) const override;
|
||||||
|
Texture* getTexture() const override { return texture_.get(); }
|
||||||
|
int getFontSize() const override { return fontSize_; }
|
||||||
|
float getAscent() const override { return ascent_; }
|
||||||
|
float getDescent() const override { return descent_; }
|
||||||
|
float getLineGap() const override { return lineGap_; }
|
||||||
|
float getLineHeight() const override { return ascent_ - descent_ + lineGap_; }
|
||||||
|
Vec2 measureText(const String& text) override;
|
||||||
|
bool isSDF() const override { return useSDF_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// 图集配置
|
||||||
|
static constexpr int ATLAS_WIDTH = 512;
|
||||||
|
static constexpr int ATLAS_HEIGHT = 512;
|
||||||
|
static constexpr int PADDING = 2; // 字形之间的间距
|
||||||
|
|
||||||
|
int fontSize_;
|
||||||
|
bool useSDF_;
|
||||||
|
mutable std::unique_ptr<GLTexture> texture_;
|
||||||
|
mutable std::unordered_map<char32_t, Glyph> glyphs_;
|
||||||
|
|
||||||
|
// stb_rect_pack 上下文
|
||||||
|
mutable stbrp_context packContext_;
|
||||||
|
mutable std::vector<stbrp_node> packNodes_;
|
||||||
|
mutable int currentY_;
|
||||||
|
|
||||||
|
std::vector<unsigned char> fontData_;
|
||||||
|
stbtt_fontinfo fontInfo_;
|
||||||
|
float scale_;
|
||||||
|
float ascent_;
|
||||||
|
float descent_;
|
||||||
|
float lineGap_;
|
||||||
|
|
||||||
|
void createAtlas();
|
||||||
|
void cacheGlyph(char32_t codepoint) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/graphics/render_backend.h>
|
||||||
|
#include <easy2d/graphics/opengl/gl_shader.h>
|
||||||
|
#include <easy2d/graphics/opengl/gl_sprite_batch.h>
|
||||||
|
|
||||||
|
// 使用标准 GLES3.2
|
||||||
|
#include <GLES3/gl32.h>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
class Window;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// OpenGL 渲染器实现
|
||||||
|
// ============================================================================
|
||||||
|
class GLRenderer : public RenderBackend {
|
||||||
|
public:
|
||||||
|
GLRenderer();
|
||||||
|
~GLRenderer() override;
|
||||||
|
|
||||||
|
// RenderBackend 接口实现
|
||||||
|
bool init(Window* window) override;
|
||||||
|
void shutdown() override;
|
||||||
|
|
||||||
|
void beginFrame(const Color& clearColor) override;
|
||||||
|
void endFrame() override;
|
||||||
|
void setViewport(int x, int y, int width, int height) override;
|
||||||
|
void setVSync(bool enabled) override;
|
||||||
|
|
||||||
|
void setBlendMode(BlendMode mode) override;
|
||||||
|
void setViewProjection(const glm::mat4& matrix) override;
|
||||||
|
|
||||||
|
Ptr<Texture> createTexture(int width, int height, const uint8_t* pixels, int channels) override;
|
||||||
|
Ptr<Texture> loadTexture(const std::string& filepath) override;
|
||||||
|
|
||||||
|
void beginSpriteBatch() override;
|
||||||
|
void drawSprite(const Texture& texture, const Rect& destRect, const Rect& srcRect,
|
||||||
|
const Color& tint, float rotation, const Vec2& anchor) override;
|
||||||
|
void drawSprite(const Texture& texture, const Vec2& position, const Color& tint) override;
|
||||||
|
void endSpriteBatch() override;
|
||||||
|
|
||||||
|
void drawLine(const Vec2& start, const Vec2& end, const Color& color, float width) override;
|
||||||
|
void drawRect(const Rect& rect, const Color& color, float width) override;
|
||||||
|
void fillRect(const Rect& rect, const Color& color) override;
|
||||||
|
void drawCircle(const Vec2& center, float radius, const Color& color, int segments, float width) override;
|
||||||
|
void fillCircle(const Vec2& center, float radius, const Color& color, int segments) override;
|
||||||
|
void drawTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3, const Color& color, float width) override;
|
||||||
|
void fillTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3, const Color& color) override;
|
||||||
|
void drawPolygon(const std::vector<Vec2>& points, const Color& color, float width) override;
|
||||||
|
void fillPolygon(const std::vector<Vec2>& points, const Color& color) override;
|
||||||
|
|
||||||
|
Ptr<FontAtlas> createFontAtlas(const std::string& filepath, int fontSize, bool useSDF = false) override;
|
||||||
|
void drawText(const FontAtlas& font, const String& text, const Vec2& position, const Color& color) override;
|
||||||
|
void drawText(const FontAtlas& font, const String& text, float x, float y, const Color& color) override;
|
||||||
|
|
||||||
|
Stats getStats() const override { return stats_; }
|
||||||
|
void resetStats() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Window* window_;
|
||||||
|
GLSpriteBatch spriteBatch_;
|
||||||
|
GLShader shapeShader_;
|
||||||
|
|
||||||
|
GLuint shapeVao_;
|
||||||
|
GLuint shapeVbo_;
|
||||||
|
|
||||||
|
glm::mat4 viewProjection_;
|
||||||
|
Stats stats_;
|
||||||
|
bool vsync_;
|
||||||
|
|
||||||
|
void initShapeRendering();
|
||||||
|
void setupBlendMode(BlendMode mode);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// 使用标准 GLES3.2
|
||||||
|
#include <GLES3/gl32.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <glm/mat4x4.hpp>
|
||||||
|
#include <glm/vec2.hpp>
|
||||||
|
#include <glm/vec3.hpp>
|
||||||
|
#include <glm/vec4.hpp>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// OpenGL Shader 程序
|
||||||
|
// ============================================================================
|
||||||
|
class GLShader {
|
||||||
|
public:
|
||||||
|
GLShader();
|
||||||
|
~GLShader();
|
||||||
|
|
||||||
|
// 从源码编译
|
||||||
|
bool compileFromSource(const char* vertexSource, const char* fragmentSource);
|
||||||
|
|
||||||
|
// 从文件加载并编译
|
||||||
|
bool compileFromFile(const std::string& vertexPath, const std::string& fragmentPath);
|
||||||
|
|
||||||
|
// 使用/激活
|
||||||
|
void bind() const;
|
||||||
|
void unbind() const;
|
||||||
|
|
||||||
|
// Uniform 设置
|
||||||
|
void setBool(const std::string& name, bool value);
|
||||||
|
void setInt(const std::string& name, int value);
|
||||||
|
void setFloat(const std::string& name, float value);
|
||||||
|
void setVec2(const std::string& name, const glm::vec2& value);
|
||||||
|
void setVec3(const std::string& name, const glm::vec3& value);
|
||||||
|
void setVec4(const std::string& name, const glm::vec4& value);
|
||||||
|
void setMat4(const std::string& name, const glm::mat4& value);
|
||||||
|
|
||||||
|
// 获取程序 ID
|
||||||
|
GLuint getProgramID() const { return programID_; }
|
||||||
|
|
||||||
|
// 检查是否有效
|
||||||
|
bool isValid() const { return programID_ != 0; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
GLuint programID_;
|
||||||
|
std::unordered_map<std::string, GLint> uniformCache_;
|
||||||
|
|
||||||
|
GLuint compileShader(GLenum type, const char* source);
|
||||||
|
GLint getUniformLocation(const std::string& name);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/core/color.h>
|
||||||
|
#include <easy2d/core/math_types.h>
|
||||||
|
#include <easy2d/graphics/texture.h>
|
||||||
|
#include <easy2d/graphics/opengl/gl_shader.h>
|
||||||
|
#include <glm/mat4x4.hpp>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
// 使用标准 GLES3.2
|
||||||
|
#include <GLES3/gl32.h>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// OpenGL 精灵批渲染器
|
||||||
|
// ============================================================================
|
||||||
|
class GLSpriteBatch {
|
||||||
|
public:
|
||||||
|
static constexpr size_t MAX_SPRITES = 10000;
|
||||||
|
static constexpr size_t VERTICES_PER_SPRITE = 4;
|
||||||
|
static constexpr size_t INDICES_PER_SPRITE = 6;
|
||||||
|
|
||||||
|
struct Vertex {
|
||||||
|
glm::vec2 position;
|
||||||
|
glm::vec2 texCoord;
|
||||||
|
glm::vec4 color;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SpriteData {
|
||||||
|
glm::vec2 position;
|
||||||
|
glm::vec2 size;
|
||||||
|
glm::vec2 texCoordMin;
|
||||||
|
glm::vec2 texCoordMax;
|
||||||
|
glm::vec4 color;
|
||||||
|
float rotation;
|
||||||
|
glm::vec2 anchor;
|
||||||
|
bool isSDF = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
GLSpriteBatch();
|
||||||
|
~GLSpriteBatch();
|
||||||
|
|
||||||
|
bool init();
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
void begin(const glm::mat4& viewProjection);
|
||||||
|
void draw(const Texture& texture, const SpriteData& data);
|
||||||
|
void end();
|
||||||
|
|
||||||
|
// 统计
|
||||||
|
uint32_t getDrawCallCount() const { return drawCallCount_; }
|
||||||
|
uint32_t getSpriteCount() const { return spriteCount_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
GLuint vao_;
|
||||||
|
GLuint vbo_;
|
||||||
|
GLuint ibo_;
|
||||||
|
GLShader shader_;
|
||||||
|
|
||||||
|
std::vector<Vertex> vertices_;
|
||||||
|
std::vector<GLuint> indices_;
|
||||||
|
|
||||||
|
const Texture* currentTexture_;
|
||||||
|
bool currentIsSDF_;
|
||||||
|
glm::mat4 viewProjection_;
|
||||||
|
|
||||||
|
uint32_t drawCallCount_;
|
||||||
|
uint32_t spriteCount_;
|
||||||
|
|
||||||
|
void flush();
|
||||||
|
void setupShader();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/graphics/texture.h>
|
||||||
|
#include <easy2d/graphics/alpha_mask.h>
|
||||||
|
|
||||||
|
// 使用标准 GLES3.2
|
||||||
|
#include <GLES3/gl32.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// OpenGL 纹理实现
|
||||||
|
// ============================================================================
|
||||||
|
class GLTexture : public Texture {
|
||||||
|
public:
|
||||||
|
GLTexture(int width, int height, const uint8_t* pixels, int channels);
|
||||||
|
GLTexture(const std::string& filepath);
|
||||||
|
~GLTexture();
|
||||||
|
|
||||||
|
// Texture 接口实现
|
||||||
|
int getWidth() const override { return width_; }
|
||||||
|
int getHeight() const override { return height_; }
|
||||||
|
Size getSize() const override { return Size(static_cast<float>(width_), static_cast<float>(height_)); }
|
||||||
|
int getChannels() const override { return channels_; }
|
||||||
|
PixelFormat getFormat() const override;
|
||||||
|
void* getNativeHandle() const override { return reinterpret_cast<void*>(static_cast<uintptr_t>(textureID_)); }
|
||||||
|
bool isValid() const override { return textureID_ != 0; }
|
||||||
|
void setFilter(bool linear) override;
|
||||||
|
void setWrap(bool repeat) override;
|
||||||
|
|
||||||
|
// 从参数创建纹理的工厂方法
|
||||||
|
static Ptr<Texture> create(int width, int height, PixelFormat format);
|
||||||
|
|
||||||
|
// 加载压缩纹理(KTX/DDS 格式)
|
||||||
|
bool loadCompressed(const std::string& filepath);
|
||||||
|
|
||||||
|
// OpenGL 特定
|
||||||
|
GLuint getTextureID() const { return textureID_; }
|
||||||
|
void bind(unsigned int slot = 0) const;
|
||||||
|
void unbind() const;
|
||||||
|
|
||||||
|
// 获取纹理数据大小(字节),用于 VRAM 跟踪
|
||||||
|
size_t getDataSize() const { return dataSize_; }
|
||||||
|
|
||||||
|
// Alpha 遮罩
|
||||||
|
bool hasAlphaMask() const { return alphaMask_ != nullptr && alphaMask_->isValid(); }
|
||||||
|
const AlphaMask* getAlphaMask() const { return alphaMask_.get(); }
|
||||||
|
void generateAlphaMask(); // 从当前纹理数据生成遮罩
|
||||||
|
|
||||||
|
private:
|
||||||
|
GLuint textureID_;
|
||||||
|
int width_;
|
||||||
|
int height_;
|
||||||
|
int channels_;
|
||||||
|
PixelFormat format_;
|
||||||
|
size_t dataSize_;
|
||||||
|
|
||||||
|
// 原始像素数据(用于生成遮罩)
|
||||||
|
std::vector<uint8_t> pixelData_;
|
||||||
|
std::unique_ptr<AlphaMask> alphaMask_;
|
||||||
|
|
||||||
|
void createTexture(const uint8_t* pixels);
|
||||||
|
|
||||||
|
// KTX 文件加载
|
||||||
|
bool loadKTX(const std::string& filepath);
|
||||||
|
// DDS 文件加载
|
||||||
|
bool loadDDS(const std::string& filepath);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/core/color.h>
|
||||||
|
#include <easy2d/core/math_types.h>
|
||||||
|
#include <easy2d/core/string.h>
|
||||||
|
#include <glm/mat4x4.hpp>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
class Window;
|
||||||
|
class Texture;
|
||||||
|
class FontAtlas;
|
||||||
|
class Shader;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 渲染后端类型
|
||||||
|
// ============================================================================
|
||||||
|
enum class BackendType {
|
||||||
|
OpenGL,
|
||||||
|
// Vulkan,
|
||||||
|
// Metal,
|
||||||
|
// D3D11,
|
||||||
|
// D3D12
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 混合模式
|
||||||
|
// ============================================================================
|
||||||
|
enum class BlendMode {
|
||||||
|
None, // 不混合
|
||||||
|
Alpha, // 标准 Alpha 混合
|
||||||
|
Additive, // 加法混合
|
||||||
|
Multiply // 乘法混合
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 渲染后端抽象接口
|
||||||
|
// ============================================================================
|
||||||
|
class RenderBackend {
|
||||||
|
public:
|
||||||
|
virtual ~RenderBackend() = default;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 生命周期
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
virtual bool init(Window* window) = 0;
|
||||||
|
virtual void shutdown() = 0;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 帧管理
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
virtual void beginFrame(const Color& clearColor) = 0;
|
||||||
|
virtual void endFrame() = 0;
|
||||||
|
virtual void setViewport(int x, int y, int width, int height) = 0;
|
||||||
|
virtual void setVSync(bool enabled) = 0;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 状态设置
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
virtual void setBlendMode(BlendMode mode) = 0;
|
||||||
|
virtual void setViewProjection(const glm::mat4& matrix) = 0;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 纹理
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
virtual Ptr<Texture> createTexture(int width, int height, const uint8_t* pixels, int channels) = 0;
|
||||||
|
virtual Ptr<Texture> loadTexture(const std::string& filepath) = 0;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 精灵批渲染
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
virtual void beginSpriteBatch() = 0;
|
||||||
|
virtual void drawSprite(const Texture& texture,
|
||||||
|
const Rect& destRect,
|
||||||
|
const Rect& srcRect,
|
||||||
|
const Color& tint,
|
||||||
|
float rotation,
|
||||||
|
const Vec2& anchor) = 0;
|
||||||
|
virtual void drawSprite(const Texture& texture,
|
||||||
|
const Vec2& position,
|
||||||
|
const Color& tint) = 0;
|
||||||
|
virtual void endSpriteBatch() = 0;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 形状渲染
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
virtual void drawLine(const Vec2& start, const Vec2& end, const Color& color, float width = 1.0f) = 0;
|
||||||
|
virtual void drawRect(const Rect& rect, const Color& color, float width = 1.0f) = 0;
|
||||||
|
virtual void fillRect(const Rect& rect, const Color& color) = 0;
|
||||||
|
virtual void drawCircle(const Vec2& center, float radius, const Color& color, int segments = 32, float width = 1.0f) = 0;
|
||||||
|
virtual void fillCircle(const Vec2& center, float radius, const Color& color, int segments = 32) = 0;
|
||||||
|
virtual void drawTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3, const Color& color, float width = 1.0f) = 0;
|
||||||
|
virtual void fillTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3, const Color& color) = 0;
|
||||||
|
virtual void drawPolygon(const std::vector<Vec2>& points, const Color& color, float width = 1.0f) = 0;
|
||||||
|
virtual void fillPolygon(const std::vector<Vec2>& points, const Color& color) = 0;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 文字渲染
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
virtual Ptr<FontAtlas> createFontAtlas(const std::string& filepath, int fontSize, bool useSDF = false) = 0;
|
||||||
|
virtual void drawText(const FontAtlas& font, const String& text, const Vec2& position, const Color& color) = 0;
|
||||||
|
virtual void drawText(const FontAtlas& font, const String& text, float x, float y, const Color& color) = 0;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 统计信息
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
struct Stats {
|
||||||
|
uint32_t drawCalls = 0;
|
||||||
|
uint32_t triangleCount = 0;
|
||||||
|
uint32_t textureBinds = 0;
|
||||||
|
uint32_t shaderBinds = 0;
|
||||||
|
};
|
||||||
|
virtual Stats getStats() const = 0;
|
||||||
|
virtual void resetStats() = 0;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 工厂方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static UniquePtr<RenderBackend> create(BackendType type);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/core/math_types.h>
|
||||||
|
#include <easy2d/core/color.h>
|
||||||
|
#include <easy2d/core/string.h>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
class Texture;
|
||||||
|
class FontAtlas;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 渲染命令类型
|
||||||
|
// ============================================================================
|
||||||
|
enum class RenderCommandType {
|
||||||
|
Sprite,
|
||||||
|
Line,
|
||||||
|
Rect,
|
||||||
|
FilledRect,
|
||||||
|
Circle,
|
||||||
|
FilledCircle,
|
||||||
|
Triangle,
|
||||||
|
FilledTriangle,
|
||||||
|
Polygon,
|
||||||
|
FilledPolygon,
|
||||||
|
Text
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 精灵数据
|
||||||
|
// ============================================================================
|
||||||
|
struct SpriteData {
|
||||||
|
Ptr<Texture> texture;
|
||||||
|
Rect destRect;
|
||||||
|
Rect srcRect;
|
||||||
|
Color tint;
|
||||||
|
float rotation;
|
||||||
|
Vec2 anchor;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 直线数据
|
||||||
|
// ============================================================================
|
||||||
|
struct LineData {
|
||||||
|
Vec2 start;
|
||||||
|
Vec2 end;
|
||||||
|
Color color;
|
||||||
|
float width;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 矩形数据
|
||||||
|
// ============================================================================
|
||||||
|
struct RectData {
|
||||||
|
Rect rect;
|
||||||
|
Color color;
|
||||||
|
float width;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 圆形数据
|
||||||
|
// ============================================================================
|
||||||
|
struct CircleData {
|
||||||
|
Vec2 center;
|
||||||
|
float radius;
|
||||||
|
Color color;
|
||||||
|
int segments;
|
||||||
|
float width;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 三角形数据
|
||||||
|
// ============================================================================
|
||||||
|
struct TriangleData {
|
||||||
|
Vec2 p1;
|
||||||
|
Vec2 p2;
|
||||||
|
Vec2 p3;
|
||||||
|
Color color;
|
||||||
|
float width;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 多边形数据
|
||||||
|
// ============================================================================
|
||||||
|
struct PolygonData {
|
||||||
|
std::vector<Vec2> points;
|
||||||
|
Color color;
|
||||||
|
float width;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 文字数据
|
||||||
|
// ============================================================================
|
||||||
|
struct TextData {
|
||||||
|
Ptr<FontAtlas> font;
|
||||||
|
String text;
|
||||||
|
Vec2 position;
|
||||||
|
Color color;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 渲染命令
|
||||||
|
// ============================================================================
|
||||||
|
struct RenderCommand {
|
||||||
|
RenderCommandType type;
|
||||||
|
int zOrder;
|
||||||
|
|
||||||
|
std::variant<
|
||||||
|
SpriteData,
|
||||||
|
LineData,
|
||||||
|
RectData,
|
||||||
|
CircleData,
|
||||||
|
TriangleData,
|
||||||
|
PolygonData,
|
||||||
|
TextData
|
||||||
|
> data;
|
||||||
|
|
||||||
|
// 用于排序
|
||||||
|
bool operator<(const RenderCommand& other) const {
|
||||||
|
return zOrder < other.zOrder;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,328 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/core/color.h>
|
||||||
|
#include <easy2d/graphics/texture.h>
|
||||||
|
#include <easy2d/graphics/opengl/gl_texture.h>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 渲染目标配置
|
||||||
|
// ============================================================================
|
||||||
|
struct RenderTargetConfig {
|
||||||
|
int width = 800; // 宽度
|
||||||
|
int height = 600; // 高度
|
||||||
|
PixelFormat colorFormat = PixelFormat::RGBA8; // 颜色格式
|
||||||
|
bool hasDepth = true; // 是否包含深度缓冲
|
||||||
|
bool hasDepthBuffer = true; // 兼容旧API的别名 (同hasDepth)
|
||||||
|
bool hasStencil = false; // 是否包含模板缓冲
|
||||||
|
int samples = 1; // 多重采样数 (1 = 无MSAA)
|
||||||
|
bool autoResize = true; // 是否自动调整大小
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 渲染目标 - 基于FBO的离屏渲染
|
||||||
|
// ============================================================================
|
||||||
|
class RenderTarget {
|
||||||
|
public:
|
||||||
|
RenderTarget();
|
||||||
|
~RenderTarget();
|
||||||
|
|
||||||
|
// 禁止拷贝
|
||||||
|
RenderTarget(const RenderTarget&) = delete;
|
||||||
|
RenderTarget& operator=(const RenderTarget&) = delete;
|
||||||
|
|
||||||
|
// 允许移动
|
||||||
|
RenderTarget(RenderTarget&& other) noexcept;
|
||||||
|
RenderTarget& operator=(RenderTarget&& other) noexcept;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 创建和销毁
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建渲染目标
|
||||||
|
*/
|
||||||
|
bool create(const RenderTargetConfig& config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化渲染目标(create的别名,兼容旧API)
|
||||||
|
*/
|
||||||
|
bool init(const RenderTargetConfig& config) { return create(config); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从现有纹理创建渲染目标
|
||||||
|
*/
|
||||||
|
bool createFromTexture(Ptr<Texture> texture, bool hasDepth = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 销毁渲染目标
|
||||||
|
*/
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭渲染目标(destroy的别名,兼容旧API)
|
||||||
|
*/
|
||||||
|
void shutdown() { destroy(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否有效
|
||||||
|
*/
|
||||||
|
bool isValid() const { return fbo_ != 0; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 尺寸和格式
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
int getWidth() const { return width_; }
|
||||||
|
int getHeight() const { return height_; }
|
||||||
|
Vec2 getSize() const { return Vec2(static_cast<float>(width_), static_cast<float>(height_)); }
|
||||||
|
PixelFormat getColorFormat() const { return colorFormat_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 绑定和解绑
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绑定为当前渲染目标
|
||||||
|
*/
|
||||||
|
void bind();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 解绑(恢复默认渲染目标)
|
||||||
|
*/
|
||||||
|
void unbind();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清除渲染目标
|
||||||
|
*/
|
||||||
|
void clear(const Color& color = Colors::Transparent);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 纹理访问
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取颜色纹理
|
||||||
|
*/
|
||||||
|
Ptr<Texture> getColorTexture() const { return colorTexture_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取深度纹理(如果有)
|
||||||
|
*/
|
||||||
|
Ptr<Texture> getDepthTexture() const { return depthTexture_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 视口和裁剪
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置视口(相对于渲染目标)
|
||||||
|
*/
|
||||||
|
void setViewport(int x, int y, int width, int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取完整视口
|
||||||
|
*/
|
||||||
|
void getFullViewport(int& x, int& y, int& width, int& height) const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 工具方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 调整大小(会销毁并重新创建)
|
||||||
|
*/
|
||||||
|
bool resize(int width, int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 复制到另一个渲染目标
|
||||||
|
*/
|
||||||
|
void copyTo(RenderTarget& target);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 复制到另一个渲染目标(blitTo的别名,兼容旧API)
|
||||||
|
* @param target 目标渲染目标
|
||||||
|
* @param color 是否复制颜色缓冲
|
||||||
|
* @param depth 是否复制深度缓冲
|
||||||
|
*/
|
||||||
|
void blitTo(RenderTarget& target, bool color = true, bool depth = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 复制到屏幕
|
||||||
|
*/
|
||||||
|
void copyToScreen(int screenWidth, int screenHeight);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 保存为图像文件
|
||||||
|
*/
|
||||||
|
bool saveToFile(const std::string& filepath);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 静态方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建渲染目标的静态工厂方法
|
||||||
|
*/
|
||||||
|
static Ptr<RenderTarget> createFromConfig(const RenderTargetConfig& config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前绑定的渲染目标ID
|
||||||
|
*/
|
||||||
|
static GLuint getCurrentFBO();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绑定默认渲染目标(屏幕)
|
||||||
|
*/
|
||||||
|
static void bindDefault();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取FBO ID(供内部使用)
|
||||||
|
*/
|
||||||
|
GLuint getFBO() const { return fbo_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
GLuint fbo_ = 0; // 帧缓冲对象
|
||||||
|
GLuint rbo_ = 0; // 渲染缓冲对象(深度/模板)
|
||||||
|
|
||||||
|
Ptr<Texture> colorTexture_; // 颜色纹理
|
||||||
|
Ptr<Texture> depthTexture_; // 深度纹理(可选)
|
||||||
|
|
||||||
|
int width_ = 0;
|
||||||
|
int height_ = 0;
|
||||||
|
PixelFormat colorFormat_ = PixelFormat::RGBA8;
|
||||||
|
bool hasDepth_ = false;
|
||||||
|
bool hasStencil_ = false;
|
||||||
|
int samples_ = 1;
|
||||||
|
|
||||||
|
bool createFBO();
|
||||||
|
void deleteFBO();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 多重采样渲染目标(用于MSAA)
|
||||||
|
// ============================================================================
|
||||||
|
class MultisampleRenderTarget : public RenderTarget {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 创建多重采样渲染目标
|
||||||
|
*/
|
||||||
|
bool create(int width, int height, int samples = 4);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 解析到普通渲染目标(用于显示)
|
||||||
|
*/
|
||||||
|
void resolveTo(RenderTarget& target);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 销毁渲染目标
|
||||||
|
*/
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
private:
|
||||||
|
GLuint colorRBO_ = 0; // 多重采样颜色渲染缓冲
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 渲染目标栈(用于嵌套渲染)
|
||||||
|
// ============================================================================
|
||||||
|
class RenderTargetStack {
|
||||||
|
public:
|
||||||
|
static RenderTargetStack& getInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 压入渲染目标
|
||||||
|
*/
|
||||||
|
void push(RenderTarget* target);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 弹出渲染目标
|
||||||
|
*/
|
||||||
|
void pop();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前渲染目标
|
||||||
|
*/
|
||||||
|
RenderTarget* getCurrent() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取栈大小
|
||||||
|
*/
|
||||||
|
size_t size() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清空栈
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
private:
|
||||||
|
RenderTargetStack() = default;
|
||||||
|
~RenderTargetStack() = default;
|
||||||
|
|
||||||
|
std::vector<RenderTarget*> stack_;
|
||||||
|
mutable std::mutex mutex_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 渲染目标管理器 - 全局渲染目标管理
|
||||||
|
// ============================================================================
|
||||||
|
class RenderTargetManager {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 获取单例实例
|
||||||
|
*/
|
||||||
|
static RenderTargetManager& getInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化渲染目标管理器
|
||||||
|
* @param width 默认宽度
|
||||||
|
* @param height 默认高度
|
||||||
|
*/
|
||||||
|
bool init(int width, int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭渲染目标管理器
|
||||||
|
*/
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建新的渲染目标
|
||||||
|
*/
|
||||||
|
Ptr<RenderTarget> createRenderTarget(const RenderTargetConfig& config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取默认渲染目标
|
||||||
|
*/
|
||||||
|
RenderTarget* getDefaultRenderTarget() const { return defaultRenderTarget_.get(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 调整所有受管渲染目标的大小
|
||||||
|
*/
|
||||||
|
void resize(int width, int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否已初始化
|
||||||
|
*/
|
||||||
|
bool isInitialized() const { return initialized_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
RenderTargetManager() = default;
|
||||||
|
~RenderTargetManager() = default;
|
||||||
|
RenderTargetManager(const RenderTargetManager&) = delete;
|
||||||
|
RenderTargetManager& operator=(const RenderTargetManager&) = delete;
|
||||||
|
|
||||||
|
Ptr<RenderTarget> defaultRenderTarget_;
|
||||||
|
std::vector<Ptr<RenderTarget>> renderTargets_;
|
||||||
|
bool initialized_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 便捷宏
|
||||||
|
// ============================================================================
|
||||||
|
#define E2D_RENDER_TARGET_STACK() ::easy2d::RenderTargetStack::getInstance()
|
||||||
|
#define E2D_RENDER_TARGET_MANAGER() ::easy2d::RenderTargetManager::getInstance()
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,319 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/graphics/opengl/gl_shader.h>
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/core/color.h>
|
||||||
|
#include <glm/vec4.hpp>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
struct WaterParams {
|
||||||
|
float waveSpeed = 1.0f;
|
||||||
|
float waveAmplitude = 0.02f;
|
||||||
|
float waveFrequency = 4.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct OutlineParams {
|
||||||
|
Color color = Colors::Black;
|
||||||
|
float thickness = 2.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DistortionParams {
|
||||||
|
float distortionAmount = 0.02f;
|
||||||
|
float timeScale = 1.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PixelateParams {
|
||||||
|
float pixelSize = 8.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InvertParams {
|
||||||
|
float strength = 1.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct GrayscaleParams {
|
||||||
|
float intensity = 1.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BlurParams {
|
||||||
|
float radius = 5.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace ShaderSource {
|
||||||
|
|
||||||
|
static const char* StandardVert = R"(
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
layout(location = 0) in vec2 a_position;
|
||||||
|
layout(location = 1) in vec2 a_texCoord;
|
||||||
|
layout(location = 2) in vec4 a_color;
|
||||||
|
|
||||||
|
uniform mat4 u_viewProjection;
|
||||||
|
uniform mat4 u_model;
|
||||||
|
|
||||||
|
out vec2 v_texCoord;
|
||||||
|
out vec4 v_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
||||||
|
v_texCoord = a_texCoord;
|
||||||
|
v_color = a_color;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
static const char* StandardFrag = R"(
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
in vec2 v_texCoord;
|
||||||
|
in vec4 v_color;
|
||||||
|
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform float u_opacity;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 texColor = texture(u_texture, v_texCoord);
|
||||||
|
fragColor = texColor * v_color;
|
||||||
|
fragColor.a *= u_opacity;
|
||||||
|
|
||||||
|
if (fragColor.a < 0.01) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
static const char* WaterFrag = R"(
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
in vec2 v_texCoord;
|
||||||
|
in vec4 v_color;
|
||||||
|
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform float u_waveSpeed;
|
||||||
|
uniform float u_waveAmplitude;
|
||||||
|
uniform float u_waveFrequency;
|
||||||
|
uniform float u_time;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 uv = v_texCoord;
|
||||||
|
|
||||||
|
// 水波纹效果
|
||||||
|
float wave = sin(uv.y * u_waveFrequency + u_time * u_waveSpeed) * u_waveAmplitude;
|
||||||
|
uv.x += wave;
|
||||||
|
|
||||||
|
vec4 texColor = texture(u_texture, uv);
|
||||||
|
fragColor = texColor * v_color;
|
||||||
|
|
||||||
|
if (fragColor.a < 0.01) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
static const char* OutlineFrag = R"(
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
in vec2 v_texCoord;
|
||||||
|
in vec4 v_color;
|
||||||
|
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform vec4 u_outlineColor;
|
||||||
|
uniform float u_thickness;
|
||||||
|
uniform vec2 u_textureSize;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 color = texture(u_texture, v_texCoord);
|
||||||
|
|
||||||
|
// 简单的描边检测
|
||||||
|
float alpha = 0.0;
|
||||||
|
vec2 offset = u_thickness / u_textureSize;
|
||||||
|
|
||||||
|
alpha += texture(u_texture, v_texCoord + vec2(-offset.x, 0.0)).a;
|
||||||
|
alpha += texture(u_texture, v_texCoord + vec2(offset.x, 0.0)).a;
|
||||||
|
alpha += texture(u_texture, v_texCoord + vec2(0.0, -offset.y)).a;
|
||||||
|
alpha += texture(u_texture, v_texCoord + vec2(0.0, offset.y)).a;
|
||||||
|
|
||||||
|
if (color.a < 0.1 && alpha > 0.0) {
|
||||||
|
fragColor = u_outlineColor;
|
||||||
|
} else {
|
||||||
|
fragColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fragColor.a < 0.01) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
static const char* DistortionFrag = R"(
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
in vec2 v_texCoord;
|
||||||
|
in vec4 v_color;
|
||||||
|
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform float u_distortionAmount;
|
||||||
|
uniform float u_time;
|
||||||
|
uniform float u_timeScale;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 uv = v_texCoord;
|
||||||
|
|
||||||
|
// 扭曲效果
|
||||||
|
float t = u_time * u_timeScale;
|
||||||
|
float dx = sin(uv.y * 10.0 + t) * u_distortionAmount;
|
||||||
|
float dy = cos(uv.x * 10.0 + t) * u_distortionAmount;
|
||||||
|
uv += vec2(dx, dy);
|
||||||
|
|
||||||
|
vec4 texColor = texture(u_texture, uv);
|
||||||
|
fragColor = texColor * v_color;
|
||||||
|
|
||||||
|
if (fragColor.a < 0.01) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
static const char* PixelateFrag = R"(
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
in vec2 v_texCoord;
|
||||||
|
in vec4 v_color;
|
||||||
|
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform float u_pixelSize;
|
||||||
|
uniform vec2 u_textureSize;
|
||||||
|
uniform float u_opacity;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 pixel = u_pixelSize / u_textureSize;
|
||||||
|
vec2 uv = floor(v_texCoord / pixel) * pixel + pixel * 0.5;
|
||||||
|
|
||||||
|
vec4 texColor = texture(u_texture, uv);
|
||||||
|
fragColor = texColor * v_color;
|
||||||
|
fragColor.a *= u_opacity;
|
||||||
|
|
||||||
|
if (fragColor.a < 0.01) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
static const char* InvertFrag = R"(
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
in vec2 v_texCoord;
|
||||||
|
in vec4 v_color;
|
||||||
|
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform float u_strength;
|
||||||
|
uniform float u_opacity;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 texColor = texture(u_texture, v_texCoord) * v_color;
|
||||||
|
vec3 inverted = vec3(1.0) - texColor.rgb;
|
||||||
|
texColor.rgb = mix(texColor.rgb, inverted, u_strength);
|
||||||
|
|
||||||
|
fragColor = texColor;
|
||||||
|
fragColor.a *= u_opacity;
|
||||||
|
|
||||||
|
if (fragColor.a < 0.01) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
static const char* GrayscaleFrag = R"(
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
in vec2 v_texCoord;
|
||||||
|
in vec4 v_color;
|
||||||
|
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform float u_intensity;
|
||||||
|
uniform float u_opacity;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 texColor = texture(u_texture, v_texCoord) * v_color;
|
||||||
|
|
||||||
|
float gray = dot(texColor.rgb, vec3(0.299, 0.587, 0.114));
|
||||||
|
texColor.rgb = mix(texColor.rgb, vec3(gray), u_intensity);
|
||||||
|
|
||||||
|
fragColor = texColor;
|
||||||
|
fragColor.a *= u_opacity;
|
||||||
|
|
||||||
|
if (fragColor.a < 0.01) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
static const char* BlurFrag = R"(
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
in vec2 v_texCoord;
|
||||||
|
in vec4 v_color;
|
||||||
|
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform float u_radius;
|
||||||
|
uniform vec2 u_textureSize;
|
||||||
|
uniform float u_opacity;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 texel = u_radius / u_textureSize;
|
||||||
|
|
||||||
|
vec4 sum = vec4(0.0);
|
||||||
|
sum += texture(u_texture, v_texCoord + texel * vec2(-1.0, -1.0));
|
||||||
|
sum += texture(u_texture, v_texCoord + texel * vec2( 0.0, -1.0));
|
||||||
|
sum += texture(u_texture, v_texCoord + texel * vec2( 1.0, -1.0));
|
||||||
|
sum += texture(u_texture, v_texCoord + texel * vec2(-1.0, 0.0));
|
||||||
|
sum += texture(u_texture, v_texCoord + texel * vec2( 0.0, 0.0));
|
||||||
|
sum += texture(u_texture, v_texCoord + texel * vec2( 1.0, 0.0));
|
||||||
|
sum += texture(u_texture, v_texCoord + texel * vec2(-1.0, 1.0));
|
||||||
|
sum += texture(u_texture, v_texCoord + texel * vec2( 0.0, 1.0));
|
||||||
|
sum += texture(u_texture, v_texCoord + texel * vec2( 1.0, 1.0));
|
||||||
|
|
||||||
|
vec4 texColor = sum / 9.0;
|
||||||
|
fragColor = texColor * v_color;
|
||||||
|
fragColor.a *= u_opacity;
|
||||||
|
|
||||||
|
if (fragColor.a < 0.01) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
} // namespace ShaderSource
|
||||||
|
|
||||||
|
class ShaderPreset {
|
||||||
|
public:
|
||||||
|
static Ptr<GLShader> Water(const WaterParams& params);
|
||||||
|
static Ptr<GLShader> Outline(const OutlineParams& params);
|
||||||
|
static Ptr<GLShader> Distortion(const DistortionParams& params);
|
||||||
|
static Ptr<GLShader> Pixelate(const PixelateParams& params);
|
||||||
|
static Ptr<GLShader> Invert(const InvertParams& params);
|
||||||
|
static Ptr<GLShader> Grayscale(const GrayscaleParams& params);
|
||||||
|
static Ptr<GLShader> Blur(const BlurParams& params);
|
||||||
|
|
||||||
|
static Ptr<GLShader> GrayscaleOutline(const GrayscaleParams& grayParams,
|
||||||
|
const OutlineParams& outlineParams);
|
||||||
|
static Ptr<GLShader> PixelateInvert(const PixelateParams& pixParams,
|
||||||
|
const InvertParams& invParams);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/core/color.h>
|
||||||
|
#include <easy2d/graphics/opengl/gl_shader.h>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Shader参数绑定回调
|
||||||
|
// ============================================================================
|
||||||
|
using ShaderBindCallback = std::function<void(GLShader&)>;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Shader系统 - 管理所有Shader的加载、缓存和热重载
|
||||||
|
// ============================================================================
|
||||||
|
class ShaderSystem {
|
||||||
|
public:
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 单例访问
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static ShaderSystem& getInstance();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 初始化和关闭
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool init();
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// Shader加载
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从文件加载Shader
|
||||||
|
* @param name Shader名称(用于缓存)
|
||||||
|
* @param vertPath 顶点着色器文件路径
|
||||||
|
* @param fragPath 片段着色器文件路径
|
||||||
|
* @return 加载的Shader,失败返回nullptr
|
||||||
|
*/
|
||||||
|
Ptr<GLShader> loadFromFile(const std::string& name,
|
||||||
|
const std::string& vertPath,
|
||||||
|
const std::string& fragPath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从源码字符串加载Shader
|
||||||
|
* @param name Shader名称(用于缓存)
|
||||||
|
* @param vertSource 顶点着色器源码
|
||||||
|
* @param fragSource 片段着色器源码
|
||||||
|
* @return 加载的Shader,失败返回nullptr
|
||||||
|
*/
|
||||||
|
Ptr<GLShader> loadFromSource(const std::string& name,
|
||||||
|
const std::string& vertSource,
|
||||||
|
const std::string& fragSource);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从已编译的程序获取Shader
|
||||||
|
* @param name Shader名称
|
||||||
|
* @return 缓存的Shader,不存在返回nullptr
|
||||||
|
*/
|
||||||
|
Ptr<GLShader> get(const std::string& name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查Shader是否存在
|
||||||
|
*/
|
||||||
|
bool has(const std::string& name) const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// Shader移除
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void remove(const std::string& name);
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 热重载支持
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 启用/禁用文件监视
|
||||||
|
*/
|
||||||
|
void setFileWatching(bool enable);
|
||||||
|
bool isFileWatching() const { return fileWatching_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 更新文件监视(在主循环中调用)
|
||||||
|
*/
|
||||||
|
void updateFileWatching();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 重载指定Shader
|
||||||
|
*/
|
||||||
|
bool reload(const std::string& name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 重载所有Shader
|
||||||
|
*/
|
||||||
|
void reloadAll();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 内置Shader获取
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
Ptr<GLShader> getBuiltinSpriteShader();
|
||||||
|
Ptr<GLShader> getBuiltinParticleShader();
|
||||||
|
Ptr<GLShader> getBuiltinPostProcessShader();
|
||||||
|
Ptr<GLShader> getBuiltinShapeShader();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 工具方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从文件读取文本内容
|
||||||
|
*/
|
||||||
|
static std::string readFile(const std::string& filepath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取Shader文件的最后修改时间
|
||||||
|
*/
|
||||||
|
static uint64_t getFileModifiedTime(const std::string& filepath);
|
||||||
|
|
||||||
|
private:
|
||||||
|
ShaderSystem() = default;
|
||||||
|
~ShaderSystem() = default;
|
||||||
|
ShaderSystem(const ShaderSystem&) = delete;
|
||||||
|
ShaderSystem& operator=(const ShaderSystem&) = delete;
|
||||||
|
|
||||||
|
struct ShaderInfo {
|
||||||
|
Ptr<GLShader> shader;
|
||||||
|
std::string vertPath;
|
||||||
|
std::string fragPath;
|
||||||
|
uint64_t vertModifiedTime;
|
||||||
|
uint64_t fragModifiedTime;
|
||||||
|
bool isBuiltin;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unordered_map<std::string, ShaderInfo> shaders_;
|
||||||
|
bool fileWatching_ = false;
|
||||||
|
float watchTimer_ = 0.0f;
|
||||||
|
static constexpr float WATCH_INTERVAL = 1.0f; // 检查间隔(秒)
|
||||||
|
|
||||||
|
// 内置Shader缓存
|
||||||
|
Ptr<GLShader> builtinSpriteShader_;
|
||||||
|
Ptr<GLShader> builtinParticleShader_;
|
||||||
|
Ptr<GLShader> builtinPostProcessShader_;
|
||||||
|
Ptr<GLShader> builtinShapeShader_;
|
||||||
|
|
||||||
|
bool loadBuiltinShaders();
|
||||||
|
void checkAndReload();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Shader参数包装器 - 简化Uniform设置
|
||||||
|
// ============================================================================
|
||||||
|
class ShaderParams {
|
||||||
|
public:
|
||||||
|
explicit ShaderParams(GLShader& shader);
|
||||||
|
|
||||||
|
ShaderParams& setBool(const std::string& name, bool value);
|
||||||
|
ShaderParams& setInt(const std::string& name, int value);
|
||||||
|
ShaderParams& setFloat(const std::string& name, float value);
|
||||||
|
ShaderParams& setVec2(const std::string& name, const glm::vec2& value);
|
||||||
|
ShaderParams& setVec3(const std::string& name, const glm::vec3& value);
|
||||||
|
ShaderParams& setVec4(const std::string& name, const glm::vec4& value);
|
||||||
|
ShaderParams& setMat4(const std::string& name, const glm::mat4& value);
|
||||||
|
ShaderParams& setColor(const std::string& name, const Color& color);
|
||||||
|
|
||||||
|
private:
|
||||||
|
GLShader& shader_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 便捷宏
|
||||||
|
// ============================================================================
|
||||||
|
#define E2D_SHADER_SYSTEM() ::easy2d::ShaderSystem::getInstance()
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/core/math_types.h>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 像素格式枚举
|
||||||
|
// ============================================================================
|
||||||
|
enum class PixelFormat {
|
||||||
|
R8, // 单通道灰度
|
||||||
|
RG8, // 双通道
|
||||||
|
RGB8, // RGB 24位
|
||||||
|
RGBA8, // RGBA 32位(默认)
|
||||||
|
RGB16F, // RGB 半精度浮点
|
||||||
|
RGBA16F, // RGBA 半精度浮点
|
||||||
|
RGB32F, // RGB 全精度浮点
|
||||||
|
RGBA32F, // RGBA 全精度浮点
|
||||||
|
Depth16, // 16位深度
|
||||||
|
Depth24, // 24位深度
|
||||||
|
Depth32F, // 32位浮点深度
|
||||||
|
Depth24Stencil8, // 24位深度 + 8位模板
|
||||||
|
|
||||||
|
// 压缩纹理格式
|
||||||
|
ETC2_RGB8, // ETC2 RGB 压缩
|
||||||
|
ETC2_RGBA8, // ETC2 RGBA 压缩
|
||||||
|
ASTC_4x4, // ASTC 4x4 压缩
|
||||||
|
ASTC_6x6, // ASTC 6x6 压缩
|
||||||
|
ASTC_8x8 // ASTC 8x8 压缩
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 纹理接口
|
||||||
|
// ============================================================================
|
||||||
|
class Texture {
|
||||||
|
public:
|
||||||
|
virtual ~Texture() = default;
|
||||||
|
|
||||||
|
// 获取尺寸
|
||||||
|
virtual int getWidth() const = 0;
|
||||||
|
virtual int getHeight() const = 0;
|
||||||
|
virtual Size getSize() const = 0;
|
||||||
|
|
||||||
|
// 获取通道数
|
||||||
|
virtual int getChannels() const = 0;
|
||||||
|
|
||||||
|
// 获取像素格式
|
||||||
|
virtual PixelFormat getFormat() const = 0;
|
||||||
|
|
||||||
|
// 获取原始句柄(用于底层渲染)
|
||||||
|
virtual void* getNativeHandle() const = 0;
|
||||||
|
|
||||||
|
// 是否有效
|
||||||
|
virtual bool isValid() const = 0;
|
||||||
|
|
||||||
|
// 设置过滤模式
|
||||||
|
virtual void setFilter(bool linear) = 0;
|
||||||
|
|
||||||
|
// 设置环绕模式
|
||||||
|
virtual void setWrap(bool repeat) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,194 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/graphics/texture.h>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <list>
|
||||||
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 纹理池配置
|
||||||
|
// ============================================================================
|
||||||
|
struct TexturePoolConfig {
|
||||||
|
size_t maxCacheSize = 64 * 1024 * 1024; // 最大缓存大小 (64MB)
|
||||||
|
size_t maxTextureCount = 256; // 最大纹理数量
|
||||||
|
float unloadInterval = 30.0f; // 自动清理间隔 (秒)
|
||||||
|
bool enableAsyncLoad = true; // 启用异步加载
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 纹理池 - 使用LRU缓存策略管理纹理复用
|
||||||
|
// ============================================================================
|
||||||
|
class TexturePool {
|
||||||
|
public:
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 单例访问
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static TexturePool& getInstance();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 初始化和关闭
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool init(const TexturePoolConfig& config = TexturePoolConfig{});
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 纹理获取
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从文件获取纹理(带缓存)
|
||||||
|
* @param filepath 纹理文件路径
|
||||||
|
* @return 纹理对象,失败返回nullptr
|
||||||
|
*/
|
||||||
|
Ptr<Texture> get(const std::string& filepath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 异步加载纹理
|
||||||
|
* @param filepath 纹理文件路径
|
||||||
|
* @param callback 加载完成回调
|
||||||
|
*/
|
||||||
|
void getAsync(const std::string& filepath,
|
||||||
|
std::function<void(Ptr<Texture>)> callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从内存数据创建纹理(自动缓存)
|
||||||
|
* @param name 纹理名称(用于缓存键)
|
||||||
|
* @param data 图像数据
|
||||||
|
* @param width 宽度
|
||||||
|
* @param height 高度
|
||||||
|
* @param format 像素格式
|
||||||
|
* @return 纹理对象
|
||||||
|
*/
|
||||||
|
Ptr<Texture> createFromData(const std::string& name,
|
||||||
|
const uint8_t* data,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
PixelFormat format = PixelFormat::RGBA8);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 缓存管理
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 手动添加纹理到缓存
|
||||||
|
*/
|
||||||
|
void add(const std::string& key, Ptr<Texture> texture);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从缓存移除纹理
|
||||||
|
*/
|
||||||
|
void remove(const std::string& key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查纹理是否在缓存中
|
||||||
|
*/
|
||||||
|
bool has(const std::string& key) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清空所有缓存
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清理未使用的纹理(LRU策略)
|
||||||
|
* @param targetSize 目标缓存大小
|
||||||
|
*/
|
||||||
|
void trim(size_t targetSize);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 统计信息
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前缓存的纹理数量
|
||||||
|
*/
|
||||||
|
size_t getTextureCount() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前缓存的总大小(字节)
|
||||||
|
*/
|
||||||
|
size_t getCacheSize() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取缓存命中率
|
||||||
|
*/
|
||||||
|
float getHitRate() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 打印统计信息
|
||||||
|
*/
|
||||||
|
void printStats() const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 自动清理
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 更新(在主循环中调用,用于自动清理)
|
||||||
|
*/
|
||||||
|
void update(float dt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置自动清理间隔
|
||||||
|
*/
|
||||||
|
void setAutoUnloadInterval(float interval);
|
||||||
|
|
||||||
|
private:
|
||||||
|
TexturePool() = default;
|
||||||
|
~TexturePool() = default;
|
||||||
|
TexturePool(const TexturePool&) = delete;
|
||||||
|
TexturePool& operator=(const TexturePool&) = delete;
|
||||||
|
|
||||||
|
// 缓存项
|
||||||
|
struct CacheEntry {
|
||||||
|
Ptr<Texture> texture;
|
||||||
|
size_t size; // 纹理大小(字节)
|
||||||
|
float lastAccessTime; // 最后访问时间
|
||||||
|
uint32_t accessCount; // 访问次数
|
||||||
|
};
|
||||||
|
|
||||||
|
// LRU列表(最近使用的在前面)
|
||||||
|
using LRUList = std::list<std::string>;
|
||||||
|
using LRUIterator = LRUList::iterator;
|
||||||
|
|
||||||
|
mutable std::mutex mutex_;
|
||||||
|
TexturePoolConfig config_;
|
||||||
|
|
||||||
|
// 缓存存储
|
||||||
|
std::unordered_map<std::string, CacheEntry> cache_;
|
||||||
|
std::unordered_map<std::string, LRUIterator> lruMap_;
|
||||||
|
LRUList lruList_;
|
||||||
|
|
||||||
|
// 统计
|
||||||
|
size_t totalSize_ = 0;
|
||||||
|
uint64_t hitCount_ = 0;
|
||||||
|
uint64_t missCount_ = 0;
|
||||||
|
float autoUnloadTimer_ = 0.0f;
|
||||||
|
bool initialized_ = false;
|
||||||
|
|
||||||
|
// 异步加载队列
|
||||||
|
struct AsyncLoadTask {
|
||||||
|
std::string filepath;
|
||||||
|
std::function<void(Ptr<Texture>)> callback;
|
||||||
|
};
|
||||||
|
std::vector<AsyncLoadTask> asyncTasks_;
|
||||||
|
|
||||||
|
// 内部方法
|
||||||
|
void touch(const std::string& key);
|
||||||
|
void evict();
|
||||||
|
Ptr<Texture> loadTexture(const std::string& filepath);
|
||||||
|
size_t calculateTextureSize(int width, int height, PixelFormat format);
|
||||||
|
void processAsyncTasks();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 便捷宏
|
||||||
|
// ============================================================================
|
||||||
|
#define E2D_TEXTURE_POOL() ::easy2d::TexturePool::getInstance()
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// VRAM 管理器 - 跟踪显存使用情况
|
||||||
|
// ============================================================================
|
||||||
|
class VRAMManager {
|
||||||
|
public:
|
||||||
|
static VRAMManager& getInstance();
|
||||||
|
|
||||||
|
// 纹理显存跟踪
|
||||||
|
void allocTexture(size_t size);
|
||||||
|
void freeTexture(size_t size);
|
||||||
|
|
||||||
|
// VBO/FBO 显存跟踪
|
||||||
|
void allocBuffer(size_t size);
|
||||||
|
void freeBuffer(size_t size);
|
||||||
|
|
||||||
|
// 查询显存使用情况
|
||||||
|
size_t getUsedVRAM() const;
|
||||||
|
size_t getTextureVRAM() const;
|
||||||
|
size_t getBufferVRAM() const;
|
||||||
|
size_t getAvailableVRAM() const;
|
||||||
|
|
||||||
|
// 显存预算管理
|
||||||
|
void setVRAMBudget(size_t budget);
|
||||||
|
size_t getVRAMBudget() const;
|
||||||
|
bool isOverBudget() const;
|
||||||
|
|
||||||
|
// 统计信息
|
||||||
|
void printStats() const;
|
||||||
|
|
||||||
|
// 重置计数器
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
private:
|
||||||
|
VRAMManager();
|
||||||
|
~VRAMManager() = default;
|
||||||
|
VRAMManager(const VRAMManager&) = delete;
|
||||||
|
VRAMManager& operator=(const VRAMManager&) = delete;
|
||||||
|
|
||||||
|
mutable std::mutex mutex_;
|
||||||
|
|
||||||
|
size_t textureVRAM_;
|
||||||
|
size_t bufferVRAM_;
|
||||||
|
size_t vramBudget_;
|
||||||
|
|
||||||
|
// 统计
|
||||||
|
uint32_t textureAllocCount_;
|
||||||
|
uint32_t textureFreeCount_;
|
||||||
|
uint32_t bufferAllocCount_;
|
||||||
|
uint32_t bufferFreeCount_;
|
||||||
|
size_t peakTextureVRAM_;
|
||||||
|
size_t peakBufferVRAM_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/core/math_types.h>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 鼠标按钮枚举 (保留接口兼容性)
|
||||||
|
// ============================================================================
|
||||||
|
enum class MouseButton {
|
||||||
|
Left = 0,
|
||||||
|
Right = 1,
|
||||||
|
Middle = 2,
|
||||||
|
Button4 = 3,
|
||||||
|
Button5 = 4,
|
||||||
|
Button6 = 5,
|
||||||
|
Button7 = 6,
|
||||||
|
Button8 = 7,
|
||||||
|
Count = 8
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Input 类 - SDL2 GameController + Touch 输入管理
|
||||||
|
// ============================================================================
|
||||||
|
class Input {
|
||||||
|
public:
|
||||||
|
Input();
|
||||||
|
~Input();
|
||||||
|
|
||||||
|
// 初始化 (使用 SDL2 GameController API)
|
||||||
|
void init();
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
// 每帧更新
|
||||||
|
void update();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 键盘输入 (映射到手柄按钮)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool isKeyDown(int keyCode) const;
|
||||||
|
bool isKeyPressed(int keyCode) const;
|
||||||
|
bool isKeyReleased(int keyCode) const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 手柄按钮 (通过 SDL_GameController)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool isButtonDown(int button) const;
|
||||||
|
bool isButtonPressed(int button) const;
|
||||||
|
bool isButtonReleased(int button) const;
|
||||||
|
|
||||||
|
// 摇杆
|
||||||
|
Vec2 getLeftStick() const;
|
||||||
|
Vec2 getRightStick() const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 鼠标输入 (映射到触摸屏)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool isMouseDown(MouseButton button) const;
|
||||||
|
bool isMousePressed(MouseButton button) const;
|
||||||
|
bool isMouseReleased(MouseButton button) const;
|
||||||
|
|
||||||
|
Vec2 getMousePosition() const;
|
||||||
|
Vec2 getMouseDelta() const;
|
||||||
|
float getMouseScroll() const { return 0.0f; }
|
||||||
|
float getMouseScrollDelta() const { return 0.0f; }
|
||||||
|
|
||||||
|
void setMousePosition(const Vec2& position);
|
||||||
|
void setMouseVisible(bool visible);
|
||||||
|
void setMouseLocked(bool locked);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 触摸屏
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool isTouching() const { return touching_; }
|
||||||
|
Vec2 getTouchPosition() const { return touchPosition_; }
|
||||||
|
int getTouchCount() const { return touchCount_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 便捷方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool isAnyKeyDown() const;
|
||||||
|
bool isAnyMouseDown() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr int MAX_BUTTONS = SDL_CONTROLLER_BUTTON_MAX;
|
||||||
|
|
||||||
|
SDL_GameController* controller_;
|
||||||
|
|
||||||
|
// 手柄按钮状态
|
||||||
|
std::array<bool, MAX_BUTTONS> buttonsDown_;
|
||||||
|
std::array<bool, MAX_BUTTONS> prevButtonsDown_;
|
||||||
|
|
||||||
|
// 摇杆状态
|
||||||
|
float leftStickX_;
|
||||||
|
float leftStickY_;
|
||||||
|
float rightStickX_;
|
||||||
|
float rightStickY_;
|
||||||
|
|
||||||
|
// 触摸屏状态
|
||||||
|
bool touching_;
|
||||||
|
bool prevTouching_;
|
||||||
|
Vec2 touchPosition_;
|
||||||
|
Vec2 prevTouchPosition_;
|
||||||
|
int touchCount_;
|
||||||
|
|
||||||
|
// 映射键盘 keyCode 到 SDL GameController 按钮
|
||||||
|
SDL_GameControllerButton mapKeyToButton(int keyCode) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file switch_compat.h
|
||||||
|
* @brief Nintendo Switch 兼容性头文件
|
||||||
|
*
|
||||||
|
* 提供 Switch 平台特定的兼容性定义和宏
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Switch 平台包含
|
||||||
|
// ============================================================================
|
||||||
|
#include <switch.h>
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Switch 特定的内存操作
|
||||||
|
// ============================================================================
|
||||||
|
// 不需要特殊处理,libnx已提供malloc/free
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Switch 文件系统相关
|
||||||
|
// ============================================================================
|
||||||
|
// RomFS路径前缀
|
||||||
|
#define SWITCH_ROMFS_PREFIX "romfs:/"
|
||||||
|
|
||||||
|
// RomFS 根路径常量
|
||||||
|
namespace easy2d {
|
||||||
|
namespace romfs {
|
||||||
|
static constexpr const char* ROMFS_ROOT = "romfs:/";
|
||||||
|
|
||||||
|
// 检查文件是否存在于 romfs 中
|
||||||
|
inline bool fileExists(const char* path) {
|
||||||
|
struct stat st;
|
||||||
|
return stat(path, &st) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查路径是否为 romfs 路径
|
||||||
|
inline bool isRomfsPath(const char* path) {
|
||||||
|
return path && (strncmp(path, "romfs:/", 7) == 0 || strncmp(path, "romfs:\\", 7) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建 romfs 完整路径
|
||||||
|
inline std::string makePath(const char* relativePath) {
|
||||||
|
std::string result = ROMFS_ROOT;
|
||||||
|
result += relativePath;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} // namespace romfs
|
||||||
|
} // namespace easy2d
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Switch 调试输出
|
||||||
|
// ============================================================================
|
||||||
|
#ifdef E2D_DEBUG
|
||||||
|
#define SWITCH_DEBUG_PRINTF(fmt, ...) printf("[Easy2D] " fmt "\n", ##__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define SWITCH_DEBUG_PRINTF(fmt, ...) ((void)0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Switch 特定的编译器属性
|
||||||
|
// ============================================================================
|
||||||
|
#define SWITCH_LIKELY(x) __builtin_expect(!!(x), 1)
|
||||||
|
#define SWITCH_UNLIKELY(x) __builtin_expect(!!(x), 0)
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Switch 特定的平台检查宏
|
||||||
|
// ============================================================================
|
||||||
|
#define IS_SWITCH_PLATFORM 1
|
||||||
|
|
||||||
|
#endif // __SWITCH__
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/core/string.h>
|
||||||
|
#include <easy2d/core/math_types.h>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
class EventQueue;
|
||||||
|
class Input;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 窗口配置
|
||||||
|
// ============================================================================
|
||||||
|
struct WindowConfig {
|
||||||
|
String title = "Easy2D Application";
|
||||||
|
int width = 1280;
|
||||||
|
int height = 720;
|
||||||
|
bool fullscreen = true; // Switch 始终全屏
|
||||||
|
bool resizable = false;
|
||||||
|
bool vsync = true;
|
||||||
|
int msaaSamples = 0;
|
||||||
|
bool centerWindow = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 鼠标光标形状枚举 (保留接口兼容性,Switch 上无效)
|
||||||
|
// ============================================================================
|
||||||
|
enum class CursorShape {
|
||||||
|
Arrow,
|
||||||
|
IBeam,
|
||||||
|
Crosshair,
|
||||||
|
Hand,
|
||||||
|
HResize,
|
||||||
|
VResize,
|
||||||
|
ResizeAll,
|
||||||
|
ResizeNWSE,
|
||||||
|
ResizeNESW
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Window 类 - SDL2 Window + GLES 3.2 封装
|
||||||
|
// ============================================================================
|
||||||
|
class Window {
|
||||||
|
public:
|
||||||
|
Window();
|
||||||
|
~Window();
|
||||||
|
|
||||||
|
// 创建窗口
|
||||||
|
bool create(const WindowConfig& config);
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
// 窗口操作
|
||||||
|
void pollEvents();
|
||||||
|
void swapBuffers();
|
||||||
|
bool shouldClose() const;
|
||||||
|
void setShouldClose(bool close);
|
||||||
|
|
||||||
|
// 窗口属性 (Switch 上大部分为空操作)
|
||||||
|
void setTitle(const String& title);
|
||||||
|
void setSize(int width, int height);
|
||||||
|
void setPosition(int x, int y);
|
||||||
|
void setFullscreen(bool fullscreen);
|
||||||
|
void setVSync(bool enabled);
|
||||||
|
void setResizable(bool resizable);
|
||||||
|
|
||||||
|
// 获取窗口属性
|
||||||
|
int getWidth() const { return width_; }
|
||||||
|
int getHeight() const { return height_; }
|
||||||
|
Size getSize() const { return Size(static_cast<float>(width_), static_cast<float>(height_)); }
|
||||||
|
Vec2 getPosition() const { return Vec2::Zero(); }
|
||||||
|
bool isFullscreen() const { return true; }
|
||||||
|
bool isVSync() const { return vsync_; }
|
||||||
|
|
||||||
|
// DPI 缩放 (Switch 固定 1.0)
|
||||||
|
float getContentScaleX() const { return 1.0f; }
|
||||||
|
float getContentScaleY() const { return 1.0f; }
|
||||||
|
Vec2 getContentScale() const { return Vec2(1.0f, 1.0f); }
|
||||||
|
|
||||||
|
// 窗口状态
|
||||||
|
bool isFocused() const { return true; }
|
||||||
|
bool isMinimized() const { return false; }
|
||||||
|
bool isMaximized() const { return true; }
|
||||||
|
|
||||||
|
// 获取 SDL2 窗口和 GL 上下文
|
||||||
|
SDL_Window* getSDLWindow() const { return sdlWindow_; }
|
||||||
|
SDL_GLContext getGLContext() const { return glContext_; }
|
||||||
|
|
||||||
|
// 设置/获取用户数据
|
||||||
|
void setUserData(void* data) { userData_ = data; }
|
||||||
|
void* getUserData() const { return userData_; }
|
||||||
|
|
||||||
|
// 事件队列
|
||||||
|
void setEventQueue(EventQueue* queue) { eventQueue_ = queue; }
|
||||||
|
EventQueue* getEventQueue() const { return eventQueue_; }
|
||||||
|
|
||||||
|
// 获取输入管理器
|
||||||
|
Input* getInput() const { return input_.get(); }
|
||||||
|
|
||||||
|
// 光标操作 (Switch 上为空操作)
|
||||||
|
void setCursor(CursorShape shape);
|
||||||
|
void resetCursor();
|
||||||
|
|
||||||
|
// 窗口回调
|
||||||
|
using ResizeCallback = std::function<void(int width, int height)>;
|
||||||
|
using FocusCallback = std::function<void(bool focused)>;
|
||||||
|
using CloseCallback = std::function<void()>;
|
||||||
|
|
||||||
|
void setResizeCallback(ResizeCallback callback) { resizeCallback_ = callback; }
|
||||||
|
void setFocusCallback(FocusCallback callback) { focusCallback_ = callback; }
|
||||||
|
void setCloseCallback(CloseCallback callback) { closeCallback_ = callback; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// SDL2 状态
|
||||||
|
SDL_Window* sdlWindow_;
|
||||||
|
SDL_GLContext glContext_;
|
||||||
|
|
||||||
|
int width_;
|
||||||
|
int height_;
|
||||||
|
bool vsync_;
|
||||||
|
bool shouldClose_;
|
||||||
|
void* userData_;
|
||||||
|
EventQueue* eventQueue_;
|
||||||
|
UniquePtr<Input> input_;
|
||||||
|
|
||||||
|
ResizeCallback resizeCallback_;
|
||||||
|
FocusCallback focusCallback_;
|
||||||
|
CloseCallback closeCallback_;
|
||||||
|
|
||||||
|
bool initSDL();
|
||||||
|
void deinitSDL();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/graphics/texture.h>
|
||||||
|
#include <easy2d/graphics/alpha_mask.h>
|
||||||
|
#include <easy2d/graphics/font.h>
|
||||||
|
#include <easy2d/audio/sound.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 资源管理器 - 统一管理纹理、字体、音效等资源
|
||||||
|
// ============================================================================
|
||||||
|
class ResourceManager {
|
||||||
|
public:
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 单例访问
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static ResourceManager& getInstance();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 搜索路径管理
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// 添加资源搜索路径
|
||||||
|
void addSearchPath(const std::string& path);
|
||||||
|
|
||||||
|
/// 移除资源搜索路径
|
||||||
|
void removeSearchPath(const std::string& path);
|
||||||
|
|
||||||
|
/// 清空所有搜索路径
|
||||||
|
void clearSearchPaths();
|
||||||
|
|
||||||
|
/// 获取搜索路径列表
|
||||||
|
const std::vector<std::string>& getSearchPaths() const { return searchPaths_; }
|
||||||
|
|
||||||
|
/// 查找资源文件完整路径
|
||||||
|
std::string findResourcePath(const std::string& filename) const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 纹理资源
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// 加载纹理(带缓存)
|
||||||
|
Ptr<Texture> loadTexture(const std::string& filepath);
|
||||||
|
|
||||||
|
/// 加载纹理并生成Alpha遮罩(用于不规则形状图片)
|
||||||
|
Ptr<Texture> loadTextureWithAlphaMask(const std::string& filepath);
|
||||||
|
|
||||||
|
/// 通过key获取已缓存的纹理
|
||||||
|
Ptr<Texture> getTexture(const std::string& key) const;
|
||||||
|
|
||||||
|
/// 检查纹理是否已缓存
|
||||||
|
bool hasTexture(const std::string& key) const;
|
||||||
|
|
||||||
|
/// 卸载指定纹理
|
||||||
|
void unloadTexture(const std::string& key);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// Alpha遮罩资源
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// 获取纹理的Alpha遮罩(如果已生成)
|
||||||
|
const AlphaMask* getAlphaMask(const std::string& textureKey) const;
|
||||||
|
|
||||||
|
/// 为已加载的纹理生成Alpha遮罩
|
||||||
|
bool generateAlphaMask(const std::string& textureKey);
|
||||||
|
|
||||||
|
/// 检查纹理是否有Alpha遮罩
|
||||||
|
bool hasAlphaMask(const std::string& textureKey) const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 字体图集资源
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// 加载字体图集(带缓存)
|
||||||
|
Ptr<FontAtlas> loadFont(const std::string& filepath, int fontSize, bool useSDF = false);
|
||||||
|
|
||||||
|
/// 通过key获取已缓存的字体图集
|
||||||
|
Ptr<FontAtlas> getFont(const std::string& key) const;
|
||||||
|
|
||||||
|
/// 检查字体是否已缓存
|
||||||
|
bool hasFont(const std::string& key) const;
|
||||||
|
|
||||||
|
/// 卸载指定字体
|
||||||
|
void unloadFont(const std::string& key);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 音效资源
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// 加载音效(带缓存)
|
||||||
|
Ptr<Sound> loadSound(const std::string& filepath);
|
||||||
|
Ptr<Sound> loadSound(const std::string& name, const std::string& filepath);
|
||||||
|
|
||||||
|
/// 通过key获取已缓存的音效
|
||||||
|
Ptr<Sound> getSound(const std::string& key) const;
|
||||||
|
|
||||||
|
/// 检查音效是否已缓存
|
||||||
|
bool hasSound(const std::string& key) const;
|
||||||
|
|
||||||
|
/// 卸载指定音效
|
||||||
|
void unloadSound(const std::string& key);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 缓存清理
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// 清理所有失效的弱引用(自动清理已释放的资源)
|
||||||
|
void purgeUnused();
|
||||||
|
|
||||||
|
/// 清理指定类型的所有缓存
|
||||||
|
void clearTextureCache();
|
||||||
|
void clearFontCache();
|
||||||
|
void clearSoundCache();
|
||||||
|
|
||||||
|
/// 清理所有资源缓存
|
||||||
|
void clearAllCaches();
|
||||||
|
|
||||||
|
/// 获取各类资源的缓存数量
|
||||||
|
size_t getTextureCacheSize() const;
|
||||||
|
size_t getFontCacheSize() const;
|
||||||
|
size_t getSoundCacheSize() const;
|
||||||
|
|
||||||
|
ResourceManager();
|
||||||
|
~ResourceManager();
|
||||||
|
ResourceManager(const ResourceManager&) = delete;
|
||||||
|
ResourceManager& operator=(const ResourceManager&) = delete;
|
||||||
|
|
||||||
|
// 生成字体缓存key
|
||||||
|
std::string makeFontKey(const std::string& filepath, int fontSize, bool useSDF) const;
|
||||||
|
|
||||||
|
// 互斥锁保护缓存
|
||||||
|
mutable std::mutex textureMutex_;
|
||||||
|
mutable std::mutex fontMutex_;
|
||||||
|
mutable std::mutex soundMutex_;
|
||||||
|
|
||||||
|
// 搜索路径
|
||||||
|
std::vector<std::string> searchPaths_;
|
||||||
|
|
||||||
|
// 资源缓存 - 使用弱指针实现自动清理
|
||||||
|
std::unordered_map<std::string, WeakPtr<Texture>> textureCache_;
|
||||||
|
std::unordered_map<std::string, WeakPtr<FontAtlas>> fontCache_;
|
||||||
|
std::unordered_map<std::string, WeakPtr<Sound>> soundCache_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,194 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/core/math_types.h>
|
||||||
|
#include <easy2d/core/color.h>
|
||||||
|
#include <easy2d/graphics/render_backend.h>
|
||||||
|
#include <easy2d/event/event_dispatcher.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <functional>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
class Scene;
|
||||||
|
class Action;
|
||||||
|
class RenderBackend;
|
||||||
|
struct RenderCommand;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 节点基类 - 场景图的基础
|
||||||
|
// ============================================================================
|
||||||
|
class Node : public std::enable_shared_from_this<Node> {
|
||||||
|
public:
|
||||||
|
Node();
|
||||||
|
virtual ~Node();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 层级管理
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void addChild(Ptr<Node> child);
|
||||||
|
void removeChild(Ptr<Node> child);
|
||||||
|
void removeChildByName(const std::string& name);
|
||||||
|
void removeFromParent();
|
||||||
|
void removeAllChildren();
|
||||||
|
|
||||||
|
Ptr<Node> getParent() const { return parent_.lock(); }
|
||||||
|
const std::vector<Ptr<Node>>& getChildren() const { return children_; }
|
||||||
|
Ptr<Node> getChildByName(const std::string& name) const;
|
||||||
|
Ptr<Node> getChildByTag(int tag) const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 变换属性
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setPosition(const Vec2& pos);
|
||||||
|
void setPosition(float x, float y);
|
||||||
|
Vec2 getPosition() const { return position_; }
|
||||||
|
|
||||||
|
void setRotation(float degrees);
|
||||||
|
float getRotation() const { return rotation_; }
|
||||||
|
|
||||||
|
void setScale(const Vec2& scale);
|
||||||
|
void setScale(float scale);
|
||||||
|
void setScale(float x, float y);
|
||||||
|
Vec2 getScale() const { return scale_; }
|
||||||
|
|
||||||
|
void setAnchor(const Vec2& anchor);
|
||||||
|
void setAnchor(float x, float y);
|
||||||
|
Vec2 getAnchor() const { return anchor_; }
|
||||||
|
|
||||||
|
void setSkew(const Vec2& skew);
|
||||||
|
void setSkew(float x, float y);
|
||||||
|
Vec2 getSkew() const { return skew_; }
|
||||||
|
|
||||||
|
void setOpacity(float opacity);
|
||||||
|
float getOpacity() const { return opacity_; }
|
||||||
|
|
||||||
|
void setVisible(bool visible);
|
||||||
|
bool isVisible() const { return visible_; }
|
||||||
|
|
||||||
|
void setZOrder(int zOrder);
|
||||||
|
int getZOrder() const { return zOrder_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 世界变换
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
Vec2 convertToWorldSpace(const Vec2& localPos) const;
|
||||||
|
Vec2 convertToNodeSpace(const Vec2& worldPos) const;
|
||||||
|
|
||||||
|
glm::mat4 getLocalTransform() const;
|
||||||
|
glm::mat4 getWorldTransform() const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 名称和标签
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setName(const std::string& name) { name_ = name; }
|
||||||
|
const std::string& getName() const { return name_; }
|
||||||
|
|
||||||
|
void setTag(int tag) { tag_ = tag; }
|
||||||
|
int getTag() const { return tag_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 生命周期回调
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
virtual void onEnter();
|
||||||
|
virtual void onExit();
|
||||||
|
virtual void onUpdate(float dt);
|
||||||
|
virtual void onRender(RenderBackend& renderer);
|
||||||
|
virtual void onAttachToScene(Scene* scene);
|
||||||
|
virtual void onDetachFromScene();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 边界框(用于空间索引)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
virtual Rect getBoundingBox() const;
|
||||||
|
|
||||||
|
// 是否需要参与空间索引(默认 true)
|
||||||
|
void setSpatialIndexed(bool indexed) { spatialIndexed_ = indexed; }
|
||||||
|
bool isSpatialIndexed() const { return spatialIndexed_; }
|
||||||
|
|
||||||
|
// 更新空间索引(手动调用,通常在边界框变化后)
|
||||||
|
void updateSpatialIndex();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 动作系统
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void runAction(Ptr<Action> action);
|
||||||
|
void stopAllActions();
|
||||||
|
void stopAction(Ptr<Action> action);
|
||||||
|
void stopActionByTag(int tag);
|
||||||
|
Ptr<Action> getActionByTag(int tag) const;
|
||||||
|
size_t getActionCount() const { return actions_.size(); }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 事件系统
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
EventDispatcher& getEventDispatcher() { return eventDispatcher_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 内部方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void update(float dt);
|
||||||
|
void render(RenderBackend& renderer);
|
||||||
|
void sortChildren();
|
||||||
|
|
||||||
|
bool isRunning() const { return running_; }
|
||||||
|
Scene* getScene() const { return scene_; }
|
||||||
|
|
||||||
|
// 多线程渲染命令收集
|
||||||
|
virtual void collectRenderCommands(std::vector<RenderCommand>& commands, int parentZOrder = 0);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// 子类重写
|
||||||
|
virtual void onDraw(RenderBackend& renderer) {}
|
||||||
|
virtual void onUpdateNode(float dt) {}
|
||||||
|
virtual void generateRenderCommand(std::vector<RenderCommand>& commands, int zOrder) {};
|
||||||
|
|
||||||
|
// 供子类访问的内部状态
|
||||||
|
Vec2& getPositionRef() { return position_; }
|
||||||
|
Vec2& getScaleRef() { return scale_; }
|
||||||
|
Vec2& getAnchorRef() { return anchor_; }
|
||||||
|
float getRotationRef() { return rotation_; }
|
||||||
|
float getOpacityRef() { return opacity_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// 层级
|
||||||
|
WeakPtr<Node> parent_;
|
||||||
|
std::vector<Ptr<Node>> children_;
|
||||||
|
bool childrenOrderDirty_ = false;
|
||||||
|
|
||||||
|
// 变换
|
||||||
|
Vec2 position_ = Vec2::Zero();
|
||||||
|
float rotation_ = 0.0f;
|
||||||
|
Vec2 scale_ = Vec2(1.0f, 1.0f);
|
||||||
|
Vec2 anchor_ = Vec2(0.5f, 0.5f);
|
||||||
|
Vec2 skew_ = Vec2::Zero();
|
||||||
|
float opacity_ = 1.0f;
|
||||||
|
bool visible_ = true;
|
||||||
|
int zOrder_ = 0;
|
||||||
|
|
||||||
|
// 缓存
|
||||||
|
mutable bool transformDirty_ = true;
|
||||||
|
mutable glm::mat4 localTransform_;
|
||||||
|
mutable glm::mat4 worldTransform_;
|
||||||
|
|
||||||
|
// 元数据
|
||||||
|
std::string name_;
|
||||||
|
int tag_ = -1;
|
||||||
|
|
||||||
|
// 状态
|
||||||
|
bool running_ = false;
|
||||||
|
Scene* scene_ = nullptr;
|
||||||
|
bool spatialIndexed_ = true; // 是否参与空间索引
|
||||||
|
Rect lastSpatialBounds_; // 上一次的空间索引边界(用于检测变化)
|
||||||
|
|
||||||
|
// 动作
|
||||||
|
std::vector<Ptr<Action>> actions_;
|
||||||
|
|
||||||
|
// 事件
|
||||||
|
EventDispatcher eventDispatcher_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/scene/node.h>
|
||||||
|
#include <easy2d/core/color.h>
|
||||||
|
#include <easy2d/graphics/camera.h>
|
||||||
|
#include <easy2d/spatial/spatial_manager.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
struct RenderCommand;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 场景类 - 节点容器,管理整个场景图
|
||||||
|
// ============================================================================
|
||||||
|
class Scene : public Node {
|
||||||
|
public:
|
||||||
|
Scene();
|
||||||
|
~Scene() override = default;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 场景属性
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setBackgroundColor(const Color& color) { backgroundColor_ = color; }
|
||||||
|
Color getBackgroundColor() const { return backgroundColor_; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 摄像机
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setCamera(Ptr<Camera> camera);
|
||||||
|
Ptr<Camera> getCamera() const { return camera_; }
|
||||||
|
|
||||||
|
Camera* getActiveCamera() const {
|
||||||
|
return camera_ ? camera_.get() : defaultCamera_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 视口和尺寸
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void setViewportSize(float width, float height);
|
||||||
|
void setViewportSize(const Size& size);
|
||||||
|
Size getViewportSize() const { return viewportSize_; }
|
||||||
|
|
||||||
|
float getWidth() const { return viewportSize_.width; }
|
||||||
|
float getHeight() const { return viewportSize_.height; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 场景状态
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool isPaused() const { return paused_; }
|
||||||
|
void pause() { paused_ = true; }
|
||||||
|
void resume() { paused_ = false; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 渲染和更新
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void renderScene(RenderBackend& renderer);
|
||||||
|
void renderContent(RenderBackend& renderer);
|
||||||
|
void updateScene(float dt);
|
||||||
|
void collectRenderCommands(std::vector<RenderCommand>& commands);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 空间索引系统
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
SpatialManager& getSpatialManager() { return spatialManager_; }
|
||||||
|
const SpatialManager& getSpatialManager() const { return spatialManager_; }
|
||||||
|
|
||||||
|
// 启用/禁用空间索引
|
||||||
|
void setSpatialIndexingEnabled(bool enabled) { spatialIndexingEnabled_ = enabled; }
|
||||||
|
bool isSpatialIndexingEnabled() const { return spatialIndexingEnabled_; }
|
||||||
|
|
||||||
|
// 节点空间索引管理(内部使用)
|
||||||
|
void updateNodeInSpatialIndex(Node* node, const Rect& oldBounds, const Rect& newBounds);
|
||||||
|
void removeNodeFromSpatialIndex(Node* node);
|
||||||
|
|
||||||
|
// 碰撞检测查询
|
||||||
|
std::vector<Node*> queryNodesInArea(const Rect& area) const;
|
||||||
|
std::vector<Node*> queryNodesAtPoint(const Vec2& point) const;
|
||||||
|
std::vector<std::pair<Node*, Node*>> queryCollisions() const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 静态创建方法
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static Ptr<Scene> create();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onEnter() override;
|
||||||
|
void onExit() override;
|
||||||
|
|
||||||
|
friend class SceneManager;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Color backgroundColor_ = Colors::Black;
|
||||||
|
Size viewportSize_ = Size::Zero();
|
||||||
|
|
||||||
|
Ptr<Camera> camera_;
|
||||||
|
Ptr<Camera> defaultCamera_;
|
||||||
|
|
||||||
|
bool paused_ = false;
|
||||||
|
|
||||||
|
// 空间索引系统
|
||||||
|
SpatialManager spatialManager_;
|
||||||
|
bool spatialIndexingEnabled_ = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <easy2d/core/types.h>
|
||||||
|
#include <easy2d/scene/scene.h>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <functional>
|
||||||
|
#include <stack>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace easy2d {
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
struct RenderCommand;
|
||||||
|
class Transition;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 场景切换特效类型
|
||||||
|
// ============================================================================
|
||||||
|
enum class TransitionType {
|
||||||
|
None,
|
||||||
|
Fade,
|
||||||
|
SlideLeft,
|
||||||
|
SlideRight,
|
||||||
|
SlideUp,
|
||||||
|
SlideDown,
|
||||||
|
Scale,
|
||||||
|
Flip
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 场景管理器 - 管理场景的生命周期和切换
|
||||||
|
// ============================================================================
|
||||||
|
class SceneManager {
|
||||||
|
public:
|
||||||
|
using TransitionCallback = std::function<void()>;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 单例访问
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
static SceneManager& getInstance();
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 场景栈操作
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// 运行第一个场景
|
||||||
|
void runWithScene(Ptr<Scene> scene);
|
||||||
|
|
||||||
|
// 替换当前场景
|
||||||
|
void replaceScene(Ptr<Scene> scene);
|
||||||
|
void replaceScene(Ptr<Scene> scene, TransitionType transition, float duration = 0.5f);
|
||||||
|
|
||||||
|
// 压入新场景(当前场景暂停)
|
||||||
|
void pushScene(Ptr<Scene> scene);
|
||||||
|
void pushScene(Ptr<Scene> scene, TransitionType transition, float duration = 0.5f);
|
||||||
|
|
||||||
|
// 弹出当前场景(恢复上一个场景)
|
||||||
|
void popScene();
|
||||||
|
void popScene(TransitionType transition, float duration = 0.5f);
|
||||||
|
|
||||||
|
// 弹出到根场景
|
||||||
|
void popToRootScene();
|
||||||
|
void popToRootScene(TransitionType transition, float duration = 0.5f);
|
||||||
|
|
||||||
|
// 弹出到指定场景
|
||||||
|
void popToScene(const std::string& name);
|
||||||
|
void popToScene(const std::string& name, TransitionType transition, float duration = 0.5f);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 获取场景
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
Ptr<Scene> getCurrentScene() const;
|
||||||
|
Ptr<Scene> getPreviousScene() const;
|
||||||
|
Ptr<Scene> getRootScene() const;
|
||||||
|
|
||||||
|
// 通过名称获取场景
|
||||||
|
Ptr<Scene> getSceneByName(const std::string& name) const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 查询
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
size_t getSceneCount() const { return sceneStack_.size(); }
|
||||||
|
bool isEmpty() const { return sceneStack_.empty(); }
|
||||||
|
bool hasScene(const std::string& name) const;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 更新和渲染
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void update(float dt);
|
||||||
|
void render(RenderBackend& renderer);
|
||||||
|
void collectRenderCommands(std::vector<RenderCommand>& commands);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 过渡控制
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
bool isTransitioning() const { return isTransitioning_; }
|
||||||
|
void setTransitionCallback(TransitionCallback callback) { transitionCallback_ = callback; }
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 清理
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
void end();
|
||||||
|
void purgeCachedScenes();
|
||||||
|
|
||||||
|
public:
|
||||||
|
SceneManager() = default;
|
||||||
|
~SceneManager() = default;
|
||||||
|
SceneManager(const SceneManager&) = delete;
|
||||||
|
SceneManager& operator=(const SceneManager&) = delete;
|
||||||
|
|
||||||
|
// 场景切换(供 Application 使用)
|
||||||
|
void enterScene(Ptr<Scene> scene);
|
||||||
|
void enterScene(Ptr<Scene> scene, Ptr<class Transition> transition);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void doSceneSwitch();
|
||||||
|
void startTransition(Ptr<Scene> from, Ptr<Scene> to, TransitionType type, float duration, Function<void()> stackAction);
|
||||||
|
void updateTransition(float dt);
|
||||||
|
void finishTransition();
|
||||||
|
void dispatchPointerEvents(Scene& scene);
|
||||||
|
|
||||||
|
std::stack<Ptr<Scene>> sceneStack_;
|
||||||
|
std::unordered_map<std::string, Ptr<Scene>> namedScenes_;
|
||||||
|
|
||||||
|
// Transition state
|
||||||
|
bool isTransitioning_ = false;
|
||||||
|
TransitionType currentTransition_ = TransitionType::None;
|
||||||
|
float transitionDuration_ = 0.0f;
|
||||||
|
float transitionElapsed_ = 0.0f;
|
||||||
|
Ptr<Scene> outgoingScene_;
|
||||||
|
Ptr<Scene> incomingScene_;
|
||||||
|
Ptr<Transition> activeTransition_;
|
||||||
|
Function<void()> transitionStackAction_;
|
||||||
|
TransitionCallback transitionCallback_;
|
||||||
|
|
||||||
|
// Next scene to switch to (queued during transition)
|
||||||
|
Ptr<Scene> nextScene_;
|
||||||
|
bool sendCleanupToScene_ = false;
|
||||||
|
|
||||||
|
Node* hoverTarget_ = nullptr;
|
||||||
|
Node* captureTarget_ = nullptr;
|
||||||
|
Vec2 lastPointerWorld_ = Vec2::Zero();
|
||||||
|
bool hasLastPointerWorld_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace easy2d
|
||||||