Compare commits
9 Commits
583e866861
...
d3a8c6c979
| Author | SHA1 | Date |
|---|---|---|
|
|
d3a8c6c979 | |
|
|
ea1bbb891d | |
|
|
05ef543615 | |
|
|
8fc3b794d2 | |
|
|
a78e6f7a05 | |
|
|
efc9961d2d | |
|
|
e0adaa3263 | |
|
|
d06c8735bd | |
|
|
f8a7fab2e7 |
|
|
@ -0,0 +1,86 @@
|
|||
# CLAUDE.md
|
||||
|
||||
本文件为 Claude Code (claude.ai/code) 在此仓库中工作时提供指导。
|
||||
|
||||
## 项目概述
|
||||
|
||||
Extra2D 是一个使用 C++17 编写的轻量级跨平台 2D 游戏引擎。支持 Windows (MinGW)、Linux、macOS 和 Nintendo Switch。项目注释和文档使用中文。
|
||||
|
||||
## 构建系统
|
||||
|
||||
项目使用 **xmake**(非 CMake)。
|
||||
|
||||
```bash
|
||||
# 配置 (Windows/MinGW 示例)
|
||||
xmake f -p mingw -a x86_64 -m release -y
|
||||
|
||||
# 配置调试构建
|
||||
xmake f -p mingw -a x86_64 -m debug -y
|
||||
|
||||
# 构建全部
|
||||
xmake build
|
||||
|
||||
# 构建特定目标
|
||||
xmake build demo_basic
|
||||
xmake build demo_hello_module
|
||||
|
||||
# 运行示例
|
||||
xmake run demo_basic
|
||||
xmake run demo_hello_module
|
||||
|
||||
# Nintendo Switch 构建
|
||||
export DEVKITPRO=/opt/devkitpro
|
||||
xmake f -p switch -m release -y
|
||||
xmake build
|
||||
```
|
||||
|
||||
构建目标:`extra2d`(引擎静态库)、`demo_basic`、`demo_hello_module`、`hello_module_lib`。
|
||||
|
||||
调试构建定义 `E2D_DEBUG` 和 `_DEBUG`。`debug_logs` 选项启用调试日志。
|
||||
|
||||
## 架构
|
||||
|
||||
### 双系统设计:模块 + 服务
|
||||
|
||||
引擎有两个由 `Application`(单例)管理的并行系统:
|
||||
|
||||
- **模块**(`Module` 基类):处理平台级初始化,具有生命周期钩子(`setupModule`、`destroyModule`、`onUpdate`、`onRender`、`handleEvent`)。通过 `E2D_MODULE` 宏注册,在静态初始化时自动发现。按优先级排序(数值越小越先初始化)。
|
||||
- **服务**(`IService` 基类):提供通过 `ServiceLocator` 访问的运行时功能。通过 `Application` 便捷方法获取:`app.scenes()`、`app.timers()`、`app.events()`、`app.camera()`。
|
||||
|
||||
### 模块自动注册
|
||||
|
||||
模块使用 `E2D_MODULE(类名, 优先级)` 宏,放置在 `.cpp` 文件末尾、**任何命名空间之外**。这会创建一个静态的 `ModuleAutoRegister<T>` 变量,在静态初始化期间向 `ModuleRegistry` 注册。静态链接需要 `--whole-archive` 链接器标志,以防止链接器剥离这些未引用的符号。
|
||||
|
||||
内置模块优先级顺序:Logger(-1) → Config(0) → Platform(10) → Window(20) → Input(30) → Render(40)。用户模块应使用优先级 1000+。
|
||||
|
||||
### 模块上下文链
|
||||
|
||||
模块生命周期方法接收上下文对象(`UpdateContext`、`RenderContext`、`EventContext`)。**必须调用 `ctx.next()`** 以继续链式调用到下一个模块。
|
||||
|
||||
### 关键子系统
|
||||
|
||||
- **平台层**:统一的 SDL2 后端(`E2D_BACKEND_SDL2` 宏定义)。通过 `IWindow`/`IInput` 接口实现平台抽象。实现在 `Extra2D/src/platform/backends/sdl2/`。
|
||||
- **图形**:通过 glad 使用 OpenGL ES 3.2。渲染器(`gl_renderer`)、精灵批处理(`gl_sprite_batch`)、支持热重载的着色器管理。内置着色器在 `Extra2D/shaders/builtin/`(构建后复制到构建目录)。
|
||||
- **场景图**:基于树的 `Node` 层级结构,支持变换继承。`Scene` 是根节点。专用节点:`ShapeNode`、`Sprite`。场景过渡效果(淡入淡出、滑动、翻转、缩放、方块)。
|
||||
- **事件系统**:`EventType` 枚举驱动。事件使用 `std::variant` 实现类型化数据(`KeyEvent`、`MouseEvent`、`GamepadEvent`)。输入使用扫描码(非键码)。
|
||||
|
||||
## 源码布局
|
||||
|
||||
- `Extra2D/include/extra2d/` — 按子系统组织的公共头文件
|
||||
- `Extra2D/src/` — 实现,镜像 include 结构
|
||||
- `Extra2D/shaders/` — GLSL 着色器文件(复制到构建输出)
|
||||
- `examples/` — 示例程序(`basic/`、`hello_module/`)
|
||||
- `xmake/engine.lua` — 引擎静态库目标定义
|
||||
- `xmake.lua` — 根构建配置,包含示例目标
|
||||
|
||||
## 依赖
|
||||
|
||||
由 xmake 管理:`glm`、`nlohmann_json`、`libsdl2`。内嵌:`glad`、`stb_image`、`stb_truetype`、`stb_rect_pack`、`KHR`。
|
||||
|
||||
## 约定
|
||||
|
||||
- 命名空间:`extra2d`
|
||||
- 智能指针:`Ptr<T>`(shared_ptr 别名)、`SharedPtr<T>`,通过 `makeShared<T>()` 创建
|
||||
- 日志:`E2D_LOG_INFO(...)`、`E2D_LOG_WARN(...)`、`E2D_LOG_ERROR(...)`
|
||||
- 导出宏:`E2D_API` 用于 DLL 可见符号
|
||||
- 自定义模块直接编译到可执行文件(静态链接,推荐)或作为独立 DLL
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/config/platform_config.h>
|
||||
#include <extra2d/core/export.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <string>
|
||||
|
||||
|
|
@ -9,24 +9,20 @@ namespace extra2d {
|
|||
/**
|
||||
* @file app_config.h
|
||||
* @brief 应用级别配置
|
||||
*
|
||||
*
|
||||
* 本文件仅包含应用级别的配置项,不包含任何模块特定配置。
|
||||
* 各模块应该在自己的模块文件中定义配置结构,并实现 IModuleConfig 接口。
|
||||
*
|
||||
* 模块配置通过 ModuleRegistry 注册,由 ConfigManager 统一管理。
|
||||
* 这种设计遵循开闭原则,新增模块无需修改引擎核心代码。
|
||||
* 各模块在自己的模块头文件中定义配置结构(如 RenderModuleConfig)。
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief 应用配置结构体
|
||||
* 仅包含应用级别的配置项,模块配置由各模块自行管理
|
||||
*/
|
||||
struct AppConfig {
|
||||
struct E2D_API AppConfig {
|
||||
std::string appName = "Extra2D App";
|
||||
std::string appVersion = "1.0.0";
|
||||
std::string organization = "";
|
||||
std::string configFile = "config.json";
|
||||
PlatformType targetPlatform = PlatformType::Auto;
|
||||
|
||||
/**
|
||||
* @brief 创建默认配置
|
||||
|
|
|
|||
|
|
@ -1,87 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @file platform_config.h
|
||||
* @brief 平台配置接口
|
||||
*
|
||||
* 平台配置只提供平台能力信息,不再直接修改应用配置。
|
||||
* 各模块通过 IModuleConfig::applyPlatformConstraints() 处理平台约束。
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief 平台类型枚举
|
||||
*/
|
||||
enum class PlatformType {
|
||||
Auto,
|
||||
Windows,
|
||||
Switch,
|
||||
Linux,
|
||||
macOS
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 平台能力结构
|
||||
*/
|
||||
struct PlatformCapabilities {
|
||||
bool supportsWindowed = true;
|
||||
bool supportsFullscreen = true;
|
||||
bool supportsBorderless = true;
|
||||
bool supportsCursor = true;
|
||||
bool supportsCursorHide = true;
|
||||
bool supportsDPIAwareness = true;
|
||||
bool supportsVSync = true;
|
||||
bool supportsMultiMonitor = true;
|
||||
bool supportsClipboard = true;
|
||||
bool supportsGamepad = true;
|
||||
bool supportsTouch = false;
|
||||
bool supportsKeyboard = true;
|
||||
bool supportsMouse = true;
|
||||
bool supportsResize = true;
|
||||
bool supportsHighDPI = true;
|
||||
int maxTextureSize = 16384;
|
||||
int preferredScreenWidth = 1920;
|
||||
int preferredScreenHeight = 1080;
|
||||
float defaultDPI = 96.0f;
|
||||
|
||||
bool hasWindowSupport() const { return supportsWindowed || supportsFullscreen || supportsBorderless; }
|
||||
bool hasInputSupport() const { return supportsKeyboard || supportsMouse || supportsGamepad || supportsTouch; }
|
||||
bool isDesktop() const { return supportsKeyboard && supportsMouse && supportsWindowed; }
|
||||
bool isConsole() const { return !supportsWindowed && supportsGamepad; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 平台配置抽象接口
|
||||
*/
|
||||
class PlatformConfig {
|
||||
public:
|
||||
virtual ~PlatformConfig() = default;
|
||||
|
||||
virtual PlatformType platformType() const = 0;
|
||||
virtual const char* platformName() const = 0;
|
||||
virtual const PlatformCapabilities& capabilities() const = 0;
|
||||
|
||||
virtual int getRecommendedWidth() const = 0;
|
||||
virtual int getRecommendedHeight() const = 0;
|
||||
virtual bool isResolutionSupported(int width, int height) const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 创建平台配置实例
|
||||
* @param type 平台类型,默认为 Auto(自动检测)
|
||||
* @return 平台配置的智能指针
|
||||
*/
|
||||
UniquePtr<PlatformConfig> createPlatformConfig(PlatformType type = PlatformType::Auto);
|
||||
|
||||
/**
|
||||
* @brief 获取平台类型名称
|
||||
* @param type 平台类型枚举值
|
||||
* @return 平台名称字符串
|
||||
*/
|
||||
const char* getPlatformTypeName(PlatformType type);
|
||||
|
||||
}
|
||||
|
|
@ -1,210 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/config/app_config.h>
|
||||
#include <extra2d/config/platform_config.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 平台检测器工具类
|
||||
// ============================================================================
|
||||
class PlatformDetector {
|
||||
public:
|
||||
/**
|
||||
* @brief 检测当前运行平台
|
||||
* @return 当前平台的类型
|
||||
*/
|
||||
static PlatformType detect();
|
||||
|
||||
/**
|
||||
* @brief 获取平台名称字符串
|
||||
* @return 平台名称(如 "Windows", "Linux", "macOS", "Switch")
|
||||
*/
|
||||
static const char* platformName();
|
||||
|
||||
/**
|
||||
* @brief 获取指定平台类型的名称
|
||||
* @param type 平台类型
|
||||
* @return 平台名称字符串
|
||||
*/
|
||||
static const char* platformName(PlatformType type);
|
||||
|
||||
/**
|
||||
* @brief 检查当前平台是否为桌面平台
|
||||
* @return 如果是桌面平台返回 true
|
||||
*/
|
||||
static bool isDesktopPlatform();
|
||||
|
||||
/**
|
||||
* @brief 检查当前平台是否为游戏主机平台
|
||||
* @return 如果是游戏主机平台返回 true
|
||||
*/
|
||||
static bool isConsolePlatform();
|
||||
|
||||
/**
|
||||
* @brief 检查当前平台是否为移动平台
|
||||
* @return 如果是移动平台返回 true
|
||||
*/
|
||||
static bool isMobilePlatform();
|
||||
|
||||
/**
|
||||
* @brief 获取当前平台的能力
|
||||
* @return 平台能力结构
|
||||
*/
|
||||
static PlatformCapabilities capabilities();
|
||||
|
||||
/**
|
||||
* @brief 获取指定平台的能力
|
||||
* @param type 平台类型
|
||||
* @return 平台能力结构
|
||||
*/
|
||||
static PlatformCapabilities capabilities(PlatformType type);
|
||||
|
||||
/**
|
||||
* @brief 获取当前平台的默认配置
|
||||
* @return 平台默认的应用配置
|
||||
*/
|
||||
static AppConfig platformDefaults();
|
||||
|
||||
/**
|
||||
* @brief 获取指定平台的默认配置
|
||||
* @param type 平台类型
|
||||
* @return 平台默认的应用配置
|
||||
*/
|
||||
static AppConfig platformDefaults(PlatformType type);
|
||||
|
||||
/**
|
||||
* @brief 获取当前平台的推荐分辨率
|
||||
* @param width 输出宽度
|
||||
* @param height 输出高度
|
||||
*/
|
||||
static void getRecommendedResolution(int& width, int& height);
|
||||
|
||||
/**
|
||||
* @brief 获取当前平台的默认 DPI
|
||||
* @return 默认 DPI 值
|
||||
*/
|
||||
static float getDefaultDPI();
|
||||
|
||||
/**
|
||||
* @brief 检查当前平台是否支持指定功能
|
||||
* @param feature 功能名称
|
||||
* @return 如果支持返回 true
|
||||
*/
|
||||
static bool supportsFeature(const std::string& feature);
|
||||
|
||||
/**
|
||||
* @brief 获取系统内存大小
|
||||
* @return 系统内存大小(MB),如果无法获取返回 0
|
||||
*/
|
||||
static int getSystemMemoryMB();
|
||||
|
||||
/**
|
||||
* @brief 获取 CPU 核心数
|
||||
* @return CPU 核心数
|
||||
*/
|
||||
static int getCPUCoreCount();
|
||||
|
||||
/**
|
||||
* @brief 检查是否支持多线程渲染
|
||||
* @return 如果支持返回 true
|
||||
*/
|
||||
static bool supportsMultithreadedRendering();
|
||||
|
||||
/**
|
||||
* @brief 获取平台特定的配置路径
|
||||
* @param appName 应用名称
|
||||
* @return 配置文件目录路径
|
||||
*/
|
||||
static std::string getConfigPath(const std::string& appName);
|
||||
|
||||
/**
|
||||
* @brief 获取平台特定的存档路径
|
||||
* @param appName 应用名称
|
||||
* @return 存档文件目录路径
|
||||
*/
|
||||
static std::string getSavePath(const std::string& appName);
|
||||
|
||||
/**
|
||||
* @brief 获取平台特定的缓存路径
|
||||
* @param appName 应用名称
|
||||
* @return 缓存文件目录路径
|
||||
*/
|
||||
static std::string getCachePath(const std::string& appName);
|
||||
|
||||
/**
|
||||
* @brief 获取平台特定的日志路径
|
||||
* @param appName 应用名称
|
||||
* @return 日志文件目录路径
|
||||
*/
|
||||
static std::string getLogPath(const std::string& appName);
|
||||
|
||||
/**
|
||||
* @brief 获取平台特定的资源路径(Shader、纹理等)
|
||||
* Switch平台使用romfs,其他平台使用相对路径
|
||||
* @param appName 应用名称
|
||||
* @return 资源目录路径
|
||||
*/
|
||||
static std::string getResourcePath(const std::string& appName = "");
|
||||
|
||||
/**
|
||||
* @brief 获取平台特定的Shader路径
|
||||
* @param appName 应用名称
|
||||
* @return Shader目录路径
|
||||
*/
|
||||
static std::string getShaderPath(const std::string& appName = "");
|
||||
|
||||
/**
|
||||
* @brief 获取平台特定的Shader缓存路径
|
||||
* Switch平台使用sdmc,其他平台使用系统缓存目录
|
||||
* @param appName 应用名称
|
||||
* @return Shader缓存目录路径
|
||||
*/
|
||||
static std::string getShaderCachePath(const std::string& appName = "");
|
||||
|
||||
/**
|
||||
* @brief 检查平台是否使用romfs(只读文件系统)
|
||||
* @return 使用romfs返回true
|
||||
*/
|
||||
static bool usesRomfs();
|
||||
|
||||
/**
|
||||
* @brief 检查平台是否支持热重载
|
||||
* Switch平台不支持热重载(romfs只读)
|
||||
* @return 支持热重载返回true
|
||||
*/
|
||||
static bool supportsHotReload();
|
||||
|
||||
/**
|
||||
* @brief 检查平台是否为小端字节序
|
||||
* @return 如果是小端字节序返回 true
|
||||
*/
|
||||
static bool isLittleEndian();
|
||||
|
||||
/**
|
||||
* @brief 检查平台是否为大端字节序
|
||||
* @return 如果是大端字节序返回 true
|
||||
*/
|
||||
static bool isBigEndian();
|
||||
|
||||
/**
|
||||
* @brief 获取平台信息摘要
|
||||
* @return 平台信息字符串
|
||||
*/
|
||||
static std::string getPlatformSummary();
|
||||
|
||||
private:
|
||||
static PlatformCapabilities getWindowsCapabilities();
|
||||
static PlatformCapabilities getLinuxCapabilities();
|
||||
static PlatformCapabilities getMacOSCapabilities();
|
||||
static PlatformCapabilities getSwitchCapabilities();
|
||||
|
||||
static AppConfig getWindowsDefaults();
|
||||
static AppConfig getLinuxDefaults();
|
||||
static AppConfig getMacOSDefaults();
|
||||
static AppConfig getSwitchDefaults();
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
// 动态库导出宏
|
||||
// 静态库时 E2D_API 为空
|
||||
#ifndef E2D_BUILDING_DLL
|
||||
#define E2D_API
|
||||
#else
|
||||
#if defined(_WIN32) || defined(__CYGWIN__)
|
||||
#define E2D_API __declspec(dllexport)
|
||||
#else
|
||||
#define E2D_API __attribute__((visibility("default")))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// 模板类导出(不需要导出)
|
||||
#define E2D_TEMPLATE_API
|
||||
|
||||
// 内联函数导出
|
||||
#if defined(_WIN32) || defined(__CYGWIN__)
|
||||
#define E2D_INLINE __forceinline
|
||||
#else
|
||||
#define E2D_INLINE inline
|
||||
#endif
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/export.h>
|
||||
#include <extra2d/core/module_meta.h>
|
||||
#include <extra2d/core/types.h>
|
||||
|
||||
#include <initializer_list>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 模块元数据模板实现
|
||||
*/
|
||||
template<typename T>
|
||||
class ModuleMeta : public ModuleMetaBase {
|
||||
public:
|
||||
using ModuleType = T;
|
||||
|
||||
const char* name_ = nullptr;
|
||||
int priority_ = 0;
|
||||
std::vector<const char*> dependencies_;
|
||||
|
||||
const char* getName() const override { return name_; }
|
||||
int getPriority() const override { return priority_; }
|
||||
std::vector<const char*> getDependencies() const override { return dependencies_; }
|
||||
|
||||
T* create() override {
|
||||
return new T();
|
||||
}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
/**
|
||||
* @brief 模块注册辅助类(静态自动注册)
|
||||
*/
|
||||
template<typename T>
|
||||
struct ModuleAutoRegister {
|
||||
ModuleMeta<T> meta;
|
||||
|
||||
ModuleAutoRegister(const char* name, int priority, std::initializer_list<const char*> deps) {
|
||||
meta.name_ = name;
|
||||
meta.priority_ = priority;
|
||||
meta.dependencies_ = std::vector<const char*>(deps);
|
||||
ModuleRegistry::instance().registerMeta(&meta);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
} // namespace extra2d
|
||||
|
||||
// ============================================================================
|
||||
// 模块定义宏 - 静态自动注册
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief 简化版模块定义(静态自动注册)
|
||||
*
|
||||
* 用于引擎内部模块,在动态库加载时自动注册
|
||||
*/
|
||||
#define E2D_MODULE(ModuleClassName, priorityValue, ...) \
|
||||
__attribute__((used)) \
|
||||
static ::extra2d::detail::ModuleAutoRegister< ::extra2d::ModuleClassName> \
|
||||
E2D_CONCAT(_e2d_auto_reg_, ModuleClassName)( \
|
||||
#ModuleClassName, priorityValue, { __VA_ARGS__ });
|
||||
|
||||
/**
|
||||
* @brief 外部模块定义(自动生成 force_link 函数)
|
||||
*
|
||||
* 用于编译为单独 DLL 的自定义模块
|
||||
*
|
||||
* 使用示例(在模块 cpp 文件末尾):
|
||||
* } // namespace extra2d
|
||||
* E2D_MODULE_EXPORT(HelloModule, 1000)
|
||||
*/
|
||||
#define E2D_MODULE_EXPORT(ModuleClassName, priorityValue, ...) \
|
||||
E2D_MODULE(ModuleClassName, priorityValue, __VA_ARGS__) \
|
||||
extern "C" E2D_API void E2D_CONCAT(e2d_force_link_, ModuleClassName)() {}
|
||||
|
||||
/**
|
||||
* @brief 声明外部模块的 force_link 函数
|
||||
*/
|
||||
#define E2D_DECLARE_FORCE_LINK(ModuleClassName) \
|
||||
extern "C" void E2D_CONCAT(e2d_force_link_, ModuleClassName)()
|
||||
|
||||
/**
|
||||
* @brief 调用 force_link 函数
|
||||
*/
|
||||
#define E2D_CALL_FORCE_LINK(ModuleClassName) \
|
||||
E2D_CONCAT(e2d_force_link_, ModuleClassName)()
|
||||
|
||||
/**
|
||||
* @brief 强制链接外部模块(声明 + 调用)
|
||||
*
|
||||
* 在 main.cpp 开头调用,触发 DLL 静态初始化
|
||||
*/
|
||||
#define E2D_FORCE_LINK(ModuleClassName) \
|
||||
E2D_DECLARE_FORCE_LINK(ModuleClassName); \
|
||||
E2D_CALL_FORCE_LINK(ModuleClassName)
|
||||
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/export.h>
|
||||
#include <extra2d/core/types.h>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
class Module;
|
||||
|
||||
/**
|
||||
* @brief 模块元数据基类
|
||||
*
|
||||
* 提供模块的类型信息、依赖关系和创建工厂
|
||||
*/
|
||||
struct E2D_API ModuleMetaBase {
|
||||
virtual ~ModuleMetaBase() = default;
|
||||
|
||||
/**
|
||||
* @brief 获取模块名称
|
||||
*/
|
||||
virtual const char* getName() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取模块优先级
|
||||
*/
|
||||
virtual int getPriority() const { return 0; }
|
||||
|
||||
/**
|
||||
* @brief 获取模块依赖
|
||||
*/
|
||||
virtual std::vector<const char*> getDependencies() const { return {}; }
|
||||
|
||||
/**
|
||||
* @brief 创建模块实例
|
||||
*/
|
||||
virtual Module* create() = 0;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 模块注册器(自动发现)
|
||||
*
|
||||
* 单例类,管理所有模块的注册、创建和生命周期
|
||||
*/
|
||||
class E2D_API ModuleRegistry {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
*/
|
||||
static ModuleRegistry& instance();
|
||||
|
||||
/**
|
||||
* @brief 注册模块元数据
|
||||
* @param meta 模块元数据指针
|
||||
*/
|
||||
void registerMeta(ModuleMetaBase* meta);
|
||||
|
||||
/**
|
||||
* @brief 获取所有已注册模块元数据
|
||||
*/
|
||||
std::vector<ModuleMetaBase*> getAllMetas() const;
|
||||
|
||||
/**
|
||||
* @brief 按名称获取模块元数据
|
||||
*/
|
||||
ModuleMetaBase* getMeta(const char* name) const;
|
||||
|
||||
/**
|
||||
* @brief 创建并初始化所有模块
|
||||
* @return 成功返回 true
|
||||
*/
|
||||
bool createAndInitAll();
|
||||
|
||||
/**
|
||||
* @brief 销毁所有模块
|
||||
*/
|
||||
void destroyAll();
|
||||
|
||||
/**
|
||||
* @brief 获取模块实例(按类型)
|
||||
*/
|
||||
template<typename T>
|
||||
T* getModule() const {
|
||||
for (const auto& [name, ptr] : instanceMap_) {
|
||||
if (auto* derived = dynamic_cast<T*>(ptr)) {
|
||||
return derived;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取模块实例(按名称)
|
||||
*/
|
||||
Module* getModule(const char* name) const;
|
||||
|
||||
/**
|
||||
* @brief 检查模块是否存在
|
||||
*/
|
||||
bool hasModule(const char* name) const;
|
||||
|
||||
/**
|
||||
* @brief 获取所有模块实例
|
||||
*/
|
||||
std::vector<Module*> getAllModules() const;
|
||||
|
||||
/**
|
||||
* @brief 检查是否已初始化
|
||||
*/
|
||||
bool isInitialized() const { return initialized_; }
|
||||
|
||||
private:
|
||||
ModuleRegistry() = default;
|
||||
~ModuleRegistry() = default;
|
||||
ModuleRegistry(const ModuleRegistry&) = delete;
|
||||
ModuleRegistry& operator=(const ModuleRegistry&) = delete;
|
||||
|
||||
/**
|
||||
* @brief 按依赖关系拓扑排序
|
||||
*/
|
||||
std::vector<ModuleMetaBase*> sortByDependency();
|
||||
|
||||
/**
|
||||
* @brief 检测循环依赖
|
||||
*/
|
||||
bool hasCircularDependency() const;
|
||||
|
||||
std::vector<ModuleMetaBase*> metas_;
|
||||
std::vector<std::unique_ptr<Module>> instances_;
|
||||
std::unordered_map<std::string, Module*> instanceMap_;
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/service_interface.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 服务注册信息
|
||||
*/
|
||||
struct ServiceRegistration {
|
||||
std::string name;
|
||||
ServicePriority priority;
|
||||
std::function<SharedPtr<IService>()> factory;
|
||||
bool enabled = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 服务注册表
|
||||
* 管理服务的注册信息,支持延迟创建和配置
|
||||
*/
|
||||
class ServiceRegistry {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
* @return 服务注册表实例引用
|
||||
*/
|
||||
static ServiceRegistry& instance();
|
||||
|
||||
ServiceRegistry(const ServiceRegistry&) = delete;
|
||||
ServiceRegistry& operator=(const ServiceRegistry&) = delete;
|
||||
|
||||
/**
|
||||
* @brief 注册服务
|
||||
* @tparam T 服务接口类型
|
||||
* @tparam Impl 服务实现类型
|
||||
* @param name 服务名称
|
||||
* @param priority 服务优先级
|
||||
*/
|
||||
template<typename T, typename Impl>
|
||||
void registerService(const std::string& name, ServicePriority priority) {
|
||||
static_assert(std::is_base_of_v<IService, T>,
|
||||
"T must derive from IService");
|
||||
static_assert(std::is_base_of_v<T, Impl>,
|
||||
"Impl must derive from T");
|
||||
|
||||
ServiceRegistration reg;
|
||||
reg.name = name;
|
||||
reg.priority = priority;
|
||||
reg.factory = []() -> SharedPtr<IService> {
|
||||
return std::static_pointer_cast<IService>(makeShared<Impl>());
|
||||
};
|
||||
registrations_.push_back(reg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册服务(带工厂函数)
|
||||
* @tparam T 服务接口类型
|
||||
* @param name 服务名称
|
||||
* @param priority 服务优先级
|
||||
* @param factory 工厂函数
|
||||
*/
|
||||
template<typename T>
|
||||
void registerServiceWithFactory(
|
||||
const std::string& name,
|
||||
ServicePriority priority,
|
||||
std::function<SharedPtr<T>()> factory) {
|
||||
static_assert(std::is_base_of_v<IService, T>,
|
||||
"T must derive from IService");
|
||||
|
||||
ServiceRegistration reg;
|
||||
reg.name = name;
|
||||
reg.priority = priority;
|
||||
reg.factory = [factory]() -> SharedPtr<IService> {
|
||||
return std::static_pointer_cast<IService>(factory());
|
||||
};
|
||||
registrations_.push_back(reg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 启用/禁用服务
|
||||
* @param name 服务名称
|
||||
* @param enabled 是否启用
|
||||
*/
|
||||
void setServiceEnabled(const std::string& name, bool enabled);
|
||||
|
||||
/**
|
||||
* @brief 创建所有已注册的服务
|
||||
* 并注册到 ServiceLocator
|
||||
*/
|
||||
void createAllServices();
|
||||
|
||||
/**
|
||||
* @brief 获取所有注册信息
|
||||
* @return 注册信息列表
|
||||
*/
|
||||
const std::vector<ServiceRegistration>& getRegistrations() const {
|
||||
return registrations_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清空所有注册
|
||||
*/
|
||||
void clear() {
|
||||
registrations_.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
ServiceRegistry() = default;
|
||||
~ServiceRegistry() = default;
|
||||
|
||||
std::vector<ServiceRegistration> registrations_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 自动服务注册器
|
||||
* 在全局作用域使用,自动注册服务
|
||||
*/
|
||||
template<typename Interface, typename Implementation>
|
||||
class AutoServiceRegistrar {
|
||||
public:
|
||||
AutoServiceRegistrar(const std::string& name, ServicePriority priority) {
|
||||
ServiceRegistry::instance().registerService<Interface, Implementation>(
|
||||
name, priority);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#define E2D_REGISTER_SERVICE_AUTO(Interface, Implementation, Name, Priority) \
|
||||
namespace { \
|
||||
static ::extra2d::AutoServiceRegistrar<Interface, Implementation> \
|
||||
E2D_CONCAT(auto_service_registrar_, __LINE__)(Name, Priority); \
|
||||
}
|
||||
|
|
@ -17,39 +17,39 @@ enum class EventType {
|
|||
WindowClose,
|
||||
WindowResize,
|
||||
WindowFocus,
|
||||
WindowLostFocus,
|
||||
WindowMoved,
|
||||
WindowBlur,
|
||||
WindowMove,
|
||||
|
||||
// 键盘事件
|
||||
KeyPressed,
|
||||
KeyReleased,
|
||||
KeyPress,
|
||||
KeyRelease,
|
||||
KeyRepeat,
|
||||
|
||||
// 鼠标事件
|
||||
MouseButtonPressed,
|
||||
MouseButtonReleased,
|
||||
MouseMoved,
|
||||
MouseScrolled,
|
||||
MousePress,
|
||||
MouseRelease,
|
||||
MouseMove,
|
||||
MouseScroll,
|
||||
|
||||
// UI 事件
|
||||
UIHoverEnter,
|
||||
UIHoverExit,
|
||||
UIPressed,
|
||||
UIReleased,
|
||||
UIClicked,
|
||||
UIPress,
|
||||
UIRelease,
|
||||
UIClick,
|
||||
|
||||
// 游戏手柄事件
|
||||
GamepadConnected,
|
||||
GamepadDisconnected,
|
||||
GamepadButtonPressed,
|
||||
GamepadButtonReleased,
|
||||
GamepadAxisMoved,
|
||||
GamepadConnect,
|
||||
GamepadDisconnect,
|
||||
GamepadPress,
|
||||
GamepadRelease,
|
||||
GamepadAxis,
|
||||
|
||||
// 触摸事件 (移动端)
|
||||
TouchBegan,
|
||||
TouchMoved,
|
||||
TouchEnded,
|
||||
TouchCancelled,
|
||||
TouchBegin,
|
||||
TouchMove,
|
||||
TouchEnd,
|
||||
TouchCancel,
|
||||
|
||||
// 自定义事件
|
||||
Custom
|
||||
|
|
@ -61,13 +61,13 @@ enum class EventType {
|
|||
struct KeyEvent {
|
||||
int keyCode;
|
||||
int scancode;
|
||||
int mods; // 修饰键 (Shift, Ctrl, Alt, etc.)
|
||||
int mods;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 鼠标事件数据
|
||||
// ============================================================================
|
||||
struct MouseButtonEvent {
|
||||
struct MouseEvent {
|
||||
int button;
|
||||
int mods;
|
||||
Vec2 position;
|
||||
|
|
@ -99,7 +99,7 @@ struct WindowMoveEvent {
|
|||
// ============================================================================
|
||||
// 游戏手柄事件数据
|
||||
// ============================================================================
|
||||
struct GamepadButtonEvent {
|
||||
struct GamepadEvent {
|
||||
int gamepadId;
|
||||
int button;
|
||||
};
|
||||
|
|
@ -134,37 +134,33 @@ struct Event {
|
|||
double timestamp = 0.0;
|
||||
bool handled = false;
|
||||
|
||||
// 事件数据联合体
|
||||
std::variant<std::monostate, KeyEvent, MouseButtonEvent, MouseMoveEvent,
|
||||
std::variant<std::monostate, KeyEvent, MouseEvent, MouseMoveEvent,
|
||||
MouseScrollEvent, WindowResizeEvent, WindowMoveEvent,
|
||||
GamepadButtonEvent, GamepadAxisEvent, TouchEvent, CustomEvent>
|
||||
GamepadEvent, GamepadAxisEvent, TouchEvent, CustomEvent>
|
||||
data;
|
||||
|
||||
// 便捷访问方法
|
||||
bool isWindowEvent() const {
|
||||
return type == EventType::WindowClose || type == EventType::WindowResize ||
|
||||
type == EventType::WindowFocus ||
|
||||
type == EventType::WindowLostFocus || type == EventType::WindowMoved;
|
||||
type == EventType::WindowFocus || type == EventType::WindowBlur ||
|
||||
type == EventType::WindowMove;
|
||||
}
|
||||
|
||||
bool isKeyboardEvent() const {
|
||||
return type == EventType::KeyPressed || type == EventType::KeyReleased ||
|
||||
return type == EventType::KeyPress || type == EventType::KeyRelease ||
|
||||
type == EventType::KeyRepeat;
|
||||
}
|
||||
|
||||
bool isMouseEvent() const {
|
||||
return type == EventType::MouseButtonPressed ||
|
||||
type == EventType::MouseButtonReleased ||
|
||||
type == EventType::MouseMoved || type == EventType::MouseScrolled;
|
||||
return type == EventType::MousePress || type == EventType::MouseRelease ||
|
||||
type == EventType::MouseMove || type == EventType::MouseScroll;
|
||||
}
|
||||
|
||||
// 静态工厂方法
|
||||
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 createMousePress(int button, int mods, const Vec2 &pos);
|
||||
static Event createMouseRelease(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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -32,136 +32,34 @@ struct ShaderMetadata {
|
|||
};
|
||||
|
||||
// ============================================================================
|
||||
// ShaderLoader接口 - 支持多种文件格式加载
|
||||
// ShaderLoader - Shader文件加载
|
||||
// ============================================================================
|
||||
class IShaderLoader {
|
||||
public:
|
||||
virtual ~IShaderLoader() = default;
|
||||
|
||||
/**
|
||||
* @brief 从分离文件加载Shader (.vert + .frag)
|
||||
* @param name Shader名称
|
||||
* @param vertPath 顶点着色器文件路径
|
||||
* @param fragPath 片段着色器文件路径
|
||||
* @return 加载结果
|
||||
*/
|
||||
virtual ShaderLoadResult loadFromSeparateFiles(
|
||||
const std::string& name,
|
||||
const std::string& vertPath,
|
||||
const std::string& fragPath) = 0;
|
||||
|
||||
/**
|
||||
* @brief 从组合文件加载Shader (.shader)
|
||||
* @param path 组合Shader文件路径
|
||||
* @return 加载结果
|
||||
*/
|
||||
virtual ShaderLoadResult loadFromCombinedFile(const std::string& path) = 0;
|
||||
|
||||
/**
|
||||
* @brief 从源码字符串加载Shader
|
||||
* @param vertSource 顶点着色器源码
|
||||
* @param fragSource 片段着色器源码
|
||||
* @return 加载结果
|
||||
*/
|
||||
virtual ShaderLoadResult loadFromSource(
|
||||
const std::string& vertSource,
|
||||
const std::string& fragSource) = 0;
|
||||
|
||||
/**
|
||||
* @brief 处理Shader源码中的#include指令
|
||||
* @param source 原始源码
|
||||
* @param baseDir 基础目录
|
||||
* @param outDependencies 输出依赖列表
|
||||
* @return 处理后的源码
|
||||
*/
|
||||
virtual std::string processIncludes(
|
||||
const std::string& source,
|
||||
const std::string& baseDir,
|
||||
std::vector<std::string>& outDependencies) = 0;
|
||||
|
||||
/**
|
||||
* @brief 应用预处理器定义
|
||||
* @param source 原始源码
|
||||
* @param defines 预处理器定义列表
|
||||
* @return 处理后的源码
|
||||
*/
|
||||
virtual std::string applyDefines(
|
||||
const std::string& source,
|
||||
const std::vector<std::string>& defines) = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取Shader元数据
|
||||
* @param path Shader文件路径
|
||||
* @return 元数据
|
||||
*/
|
||||
virtual ShaderMetadata getMetadata(const std::string& path) = 0;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 默认ShaderLoader实现
|
||||
// ============================================================================
|
||||
class ShaderLoader : public IShaderLoader {
|
||||
class ShaderLoader {
|
||||
public:
|
||||
ShaderLoader();
|
||||
~ShaderLoader() override = default;
|
||||
~ShaderLoader() = default;
|
||||
|
||||
/**
|
||||
* @brief 从分离文件加载Shader (.vert + .frag)
|
||||
* @param name Shader名称
|
||||
* @param vertPath 顶点着色器文件路径
|
||||
* @param fragPath 片段着色器文件路径
|
||||
* @return 加载结果
|
||||
*/
|
||||
ShaderLoadResult loadFromSeparateFiles(
|
||||
const std::string& name,
|
||||
const std::string& vertPath,
|
||||
const std::string& fragPath) override;
|
||||
const std::string& fragPath);
|
||||
|
||||
/**
|
||||
* @brief 从组合文件加载Shader (.shader)
|
||||
* @param path 组合Shader文件路径
|
||||
* @return 加载结果
|
||||
*/
|
||||
ShaderLoadResult loadFromCombinedFile(const std::string& path) override;
|
||||
ShaderLoadResult loadFromCombinedFile(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 从源码字符串加载Shader
|
||||
* @param vertSource 顶点着色器源码
|
||||
* @param fragSource 片段着色器源码
|
||||
* @return 加载结果
|
||||
*/
|
||||
ShaderLoadResult loadFromSource(
|
||||
const std::string& vertSource,
|
||||
const std::string& fragSource) override;
|
||||
const std::string& fragSource);
|
||||
|
||||
/**
|
||||
* @brief 处理Shader源码中的#include指令
|
||||
* @param source 原始源码
|
||||
* @param baseDir 基础目录
|
||||
* @param outDependencies 输出依赖列表
|
||||
* @return 处理后的源码
|
||||
*/
|
||||
std::string processIncludes(
|
||||
const std::string& source,
|
||||
const std::string& baseDir,
|
||||
std::vector<std::string>& outDependencies) override;
|
||||
std::vector<std::string>& outDependencies);
|
||||
|
||||
/**
|
||||
* @brief 应用预处理器定义
|
||||
* @param source 原始源码
|
||||
* @param defines 预处理器定义列表
|
||||
* @return 处理后的源码
|
||||
*/
|
||||
std::string applyDefines(
|
||||
const std::string& source,
|
||||
const std::vector<std::string>& defines) override;
|
||||
const std::vector<std::string>& defines);
|
||||
|
||||
/**
|
||||
* @brief 获取Shader元数据
|
||||
* @param path Shader文件路径
|
||||
* @return 元数据
|
||||
*/
|
||||
ShaderMetadata getMetadata(const std::string& path) override;
|
||||
ShaderMetadata getMetadata(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 添加include搜索路径
|
||||
|
|
|
|||
|
|
@ -382,6 +382,12 @@ public:
|
|||
*/
|
||||
void init(Scene* scene, size_t maxMemoryUsage = 0);
|
||||
|
||||
/**
|
||||
* @brief 设置渲染后端
|
||||
* @param backend 渲染后端指针
|
||||
*/
|
||||
void setRenderBackend(RenderBackend* backend) { backend_ = backend; }
|
||||
|
||||
// ========================================================================
|
||||
// 纹理加载
|
||||
// ========================================================================
|
||||
|
|
@ -554,6 +560,7 @@ private:
|
|||
void tryAutoEvict();
|
||||
|
||||
Scene* scene_; // 场景指针
|
||||
RenderBackend* backend_ = nullptr; // 渲染后端
|
||||
mutable std::mutex mutex_; // 互斥锁
|
||||
std::unordered_map<TextureKey, TexturePoolEntry, TextureKeyHash> cache_; // 纹理缓存
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,71 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/config/app_config.h>
|
||||
#include <extra2d/config/config_manager.h>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 配置模块
|
||||
* 管理应用程序配置的加载和保存
|
||||
*/
|
||||
class ConfigModule : public Module {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
*/
|
||||
ConfigModule();
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~ConfigModule() override;
|
||||
|
||||
/**
|
||||
* @brief 获取模块名称
|
||||
* @return 模块名称
|
||||
*/
|
||||
const char* getName() const override { return "Config"; }
|
||||
|
||||
/**
|
||||
* @brief 获取模块优先级
|
||||
* @return 优先级(核心模块,最先初始化)
|
||||
*/
|
||||
int getPriority() const override { return 0; }
|
||||
|
||||
/**
|
||||
* @brief 设置模块
|
||||
*/
|
||||
void setupModule() override;
|
||||
|
||||
/**
|
||||
* @brief 销毁模块
|
||||
*/
|
||||
void destroyModule() override;
|
||||
|
||||
/**
|
||||
* @brief 设置应用配置
|
||||
* @param config 应用配置
|
||||
*/
|
||||
void setAppConfig(const AppConfig& config) { appConfig_ = config; }
|
||||
|
||||
/**
|
||||
* @brief 设置配置文件路径
|
||||
* @param path 配置文件路径
|
||||
*/
|
||||
void setConfigPath(const std::string& path) { configPath_ = path; }
|
||||
|
||||
/**
|
||||
* @brief 获取配置管理器
|
||||
* @return 配置管理器引用
|
||||
*/
|
||||
ConfigManager& getManager() { return ConfigManager::instance(); }
|
||||
|
||||
private:
|
||||
AppConfig appConfig_;
|
||||
std::string configPath_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/input/input_config.h>
|
||||
#include <extra2d/platform/iinput.h>
|
||||
#include <extra2d/platform/iwindow.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 输入模块
|
||||
* 管理键盘、鼠标、手柄和触摸输入
|
||||
*/
|
||||
class InputModule : public Module {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
*/
|
||||
InputModule();
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~InputModule() override;
|
||||
|
||||
/**
|
||||
* @brief 获取模块名称
|
||||
* @return 模块名称
|
||||
*/
|
||||
const char* getName() const override { return "Input"; }
|
||||
|
||||
/**
|
||||
* @brief 获取模块优先级
|
||||
* @return 优先级
|
||||
*/
|
||||
int getPriority() const override { return 30; }
|
||||
|
||||
/**
|
||||
* @brief 设置模块
|
||||
*/
|
||||
void setupModule() override;
|
||||
|
||||
/**
|
||||
* @brief 销毁模块
|
||||
*/
|
||||
void destroyModule() override;
|
||||
|
||||
/**
|
||||
* @brief 更新时
|
||||
* @param ctx 更新上下文
|
||||
*/
|
||||
void onUpdate(UpdateContext& ctx) override;
|
||||
|
||||
/**
|
||||
* @brief 设置输入配置
|
||||
* @param config 输入配置数据
|
||||
*/
|
||||
void setInputConfig(const InputConfigData& config) { config_ = config; }
|
||||
|
||||
/**
|
||||
* @brief 获取输入接口
|
||||
* @return 输入接口指针
|
||||
*/
|
||||
IInput* getInput() const { return input_; }
|
||||
|
||||
/**
|
||||
* @brief 设置窗口
|
||||
* @param window 窗口接口指针
|
||||
*/
|
||||
void setWindow(IWindow* window);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 使用窗口初始化输入
|
||||
*/
|
||||
void initializeWithWindow();
|
||||
|
||||
IWindow* window_ = nullptr;
|
||||
IInput* input_ = nullptr;
|
||||
InputConfigData config_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <string>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 渲染模块配置
|
||||
*/
|
||||
struct RenderModuleConfig {
|
||||
BackendType backend = BackendType::OpenGL;
|
||||
bool vsync = true;
|
||||
int targetFPS = 60;
|
||||
int multisamples = 0;
|
||||
bool sRGBFramebuffer = false;
|
||||
int spriteBatchSize = 1000;
|
||||
bool tripleBuffering = false;
|
||||
Color clearColor{0.0f, 0.0f, 0.0f, 1.0f};
|
||||
int maxTextureSize = 0;
|
||||
int textureAnisotropy = 1;
|
||||
bool wireframeMode = false;
|
||||
bool depthTest = false;
|
||||
bool blending = true;
|
||||
bool dithering = false;
|
||||
int maxRenderTargets = 1;
|
||||
bool allowShaderHotReload = false;
|
||||
std::string shaderCachePath;
|
||||
|
||||
/**
|
||||
* @brief 检查是否启用多重采样
|
||||
*/
|
||||
bool isMultisampleEnabled() const { return multisamples > 0; }
|
||||
|
||||
/**
|
||||
* @brief 检查是否限制帧率
|
||||
*/
|
||||
bool isFPSCapped() const { return targetFPS > 0; }
|
||||
|
||||
/**
|
||||
* @brief 验证配置有效性
|
||||
* @return 配置有效返回 true
|
||||
*/
|
||||
bool validate() const {
|
||||
if (targetFPS < 1 || targetFPS > 240) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (multisamples != 0 && multisamples != 2 && multisamples != 4 &&
|
||||
multisamples != 8 && multisamples != 16) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (spriteBatchSize <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 渲染模块
|
||||
* 管理渲染后端的初始化和配置
|
||||
*/
|
||||
class RenderModule : public Module {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
*/
|
||||
RenderModule();
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~RenderModule() override;
|
||||
|
||||
/**
|
||||
* @brief 获取模块名称
|
||||
* @return 模块名称
|
||||
*/
|
||||
const char* getName() const override { return "Render"; }
|
||||
|
||||
/**
|
||||
* @brief 获取模块优先级
|
||||
* @return 优先级
|
||||
*/
|
||||
int getPriority() const override { return 40; }
|
||||
|
||||
/**
|
||||
* @brief 设置模块
|
||||
*/
|
||||
void setupModule() override;
|
||||
|
||||
/**
|
||||
* @brief 销毁模块
|
||||
*/
|
||||
void destroyModule() override;
|
||||
|
||||
/**
|
||||
* @brief 渲染前
|
||||
* @param ctx 渲染上下文
|
||||
*/
|
||||
void beforeRender(RenderContext& ctx) override;
|
||||
|
||||
/**
|
||||
* @brief 渲染后
|
||||
* @param ctx 渲染上下文
|
||||
*/
|
||||
void afterRender(RenderContext& ctx) override;
|
||||
|
||||
/**
|
||||
* @brief 设置渲染配置
|
||||
* @param config 渲染配置
|
||||
*/
|
||||
void setRenderConfig(const RenderModuleConfig& config) { config_ = config; }
|
||||
|
||||
/**
|
||||
* @brief 设置窗口
|
||||
* @param window 窗口接口指针
|
||||
*/
|
||||
void setWindow(IWindow* window);
|
||||
|
||||
/**
|
||||
* @brief 获取渲染器
|
||||
* @return 渲染器指针
|
||||
*/
|
||||
RenderBackend* getRenderer() const { return renderer_.get(); }
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 使用窗口初始化渲染器
|
||||
*/
|
||||
void initializeWithWindow();
|
||||
|
||||
IWindow* window_ = nullptr;
|
||||
UniquePtr<RenderBackend> renderer_;
|
||||
RenderModuleConfig config_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/platform/window_config.h>
|
||||
#include <extra2d/platform/iwindow.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 窗口模块
|
||||
* 管理窗口的创建和配置
|
||||
*/
|
||||
class WindowModule : public Module {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
*/
|
||||
WindowModule();
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~WindowModule() override;
|
||||
|
||||
/**
|
||||
* @brief 获取模块名称
|
||||
* @return 模块名称
|
||||
*/
|
||||
const char* getName() const override { return "Window"; }
|
||||
|
||||
/**
|
||||
* @brief 获取模块优先级
|
||||
* @return 优先级
|
||||
*/
|
||||
int getPriority() const override { return 20; }
|
||||
|
||||
/**
|
||||
* @brief 设置模块
|
||||
*/
|
||||
void setupModule() override;
|
||||
|
||||
/**
|
||||
* @brief 销毁模块
|
||||
*/
|
||||
void destroyModule() override;
|
||||
|
||||
/**
|
||||
* @brief 设置窗口配置
|
||||
* @param config 窗口配置数据
|
||||
*/
|
||||
void setWindowConfig(const WindowConfigData& config) { windowConfig_ = config; }
|
||||
|
||||
/**
|
||||
* @brief 获取窗口接口
|
||||
* @return 窗口接口指针
|
||||
*/
|
||||
IWindow* getWindow() const { return window_.get(); }
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 初始化 SDL2 后端
|
||||
* @return 初始化成功返回 true
|
||||
*/
|
||||
bool initSDL2();
|
||||
|
||||
/**
|
||||
* @brief 关闭 SDL2 后端
|
||||
*/
|
||||
void shutdownSDL2();
|
||||
|
||||
/**
|
||||
* @brief 创建窗口
|
||||
* @param config 窗口配置数据
|
||||
* @return 创建成功返回 true
|
||||
*/
|
||||
bool createWindow(const WindowConfigData& config);
|
||||
|
||||
bool sdl2Initialized_ = false;
|
||||
WindowConfigData windowConfig_;
|
||||
UniquePtr<IWindow> window_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -85,4 +85,4 @@ private:
|
|||
} e2d_backend_reg_##name; \
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,77 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/platform/iinput.h>
|
||||
#include <extra2d/platform/window_module.h>
|
||||
#include <typeindex>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 输入模块
|
||||
* 管理输入设备
|
||||
*/
|
||||
class InputModule : public Module {
|
||||
public:
|
||||
/**
|
||||
* @brief 配置结构
|
||||
*/
|
||||
struct Cfg {
|
||||
float deadzone;
|
||||
float mouseSensitivity;
|
||||
bool enableVibration;
|
||||
int maxGamepads;
|
||||
int priority;
|
||||
|
||||
Cfg()
|
||||
: deadzone(0.15f)
|
||||
, mouseSensitivity(1.0f)
|
||||
, enableVibration(true)
|
||||
, maxGamepads(4)
|
||||
, priority(20)
|
||||
{}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param cfg 配置
|
||||
*/
|
||||
explicit InputModule(const Cfg& cfg = Cfg{});
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~InputModule() override;
|
||||
|
||||
bool init() override;
|
||||
void shutdown() override;
|
||||
bool ok() const override { return initialized_; }
|
||||
const char* name() const override { return "input"; }
|
||||
int priority() const override { return cfg_.priority; }
|
||||
|
||||
/**
|
||||
* @brief 获取依赖
|
||||
* @return 依赖模块类型列表
|
||||
*/
|
||||
std::vector<std::type_index> deps() const override {
|
||||
return {std::type_index(typeid(WindowModule))};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取输入接口
|
||||
* @return 输入接口指针
|
||||
*/
|
||||
IInput* input() const { return input_; }
|
||||
|
||||
/**
|
||||
* @brief 更新输入状态
|
||||
*/
|
||||
void update();
|
||||
|
||||
private:
|
||||
Cfg cfg_;
|
||||
IInput* input_ = nullptr;
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,72 +1,195 @@
|
|||
#pragma once
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 键盘按键码
|
||||
* @brief 键盘按键码(跨平台统一)
|
||||
*
|
||||
* 使用 SDL Scancode 作为底层实现,确保跨平台一致性。
|
||||
* Scancode 是物理按键位置,不依赖于键盘布局。
|
||||
*/
|
||||
enum class Key : int {
|
||||
None = 0,
|
||||
A, B, C, D, E, F, G, H, I, J, K, L, M,
|
||||
N, O, P, Q, R, S, T, U, V, W, X, Y, Z,
|
||||
Num0, Num1, Num2, Num3, Num4,
|
||||
Num5, Num6, Num7, Num8, Num9,
|
||||
F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12,
|
||||
Space, Enter, Escape, Tab, Backspace,
|
||||
Insert, Delete, Home, End, PageUp, PageDown,
|
||||
Up, Down, Left, Right,
|
||||
LShift, RShift, LCtrl, RCtrl, LAlt, RAlt,
|
||||
CapsLock, NumLock, ScrollLock,
|
||||
Count
|
||||
None = 0,
|
||||
|
||||
// 字母键
|
||||
A = SDL_SCANCODE_A,
|
||||
B = SDL_SCANCODE_B,
|
||||
C = SDL_SCANCODE_C,
|
||||
D = SDL_SCANCODE_D,
|
||||
E = SDL_SCANCODE_E,
|
||||
F = SDL_SCANCODE_F,
|
||||
G = SDL_SCANCODE_G,
|
||||
H = SDL_SCANCODE_H,
|
||||
I = SDL_SCANCODE_I,
|
||||
J = SDL_SCANCODE_J,
|
||||
K = SDL_SCANCODE_K,
|
||||
L = SDL_SCANCODE_L,
|
||||
M = SDL_SCANCODE_M,
|
||||
N = SDL_SCANCODE_N,
|
||||
O = SDL_SCANCODE_O,
|
||||
P = SDL_SCANCODE_P,
|
||||
Q = SDL_SCANCODE_Q,
|
||||
R = SDL_SCANCODE_R,
|
||||
S = SDL_SCANCODE_S,
|
||||
T = SDL_SCANCODE_T,
|
||||
U = SDL_SCANCODE_U,
|
||||
V = SDL_SCANCODE_V,
|
||||
W = SDL_SCANCODE_W,
|
||||
X = SDL_SCANCODE_X,
|
||||
Y = SDL_SCANCODE_Y,
|
||||
Z = SDL_SCANCODE_Z,
|
||||
|
||||
// 数字键(主键盘)
|
||||
Num0 = SDL_SCANCODE_0,
|
||||
Num1 = SDL_SCANCODE_1,
|
||||
Num2 = SDL_SCANCODE_2,
|
||||
Num3 = SDL_SCANCODE_3,
|
||||
Num4 = SDL_SCANCODE_4,
|
||||
Num5 = SDL_SCANCODE_5,
|
||||
Num6 = SDL_SCANCODE_6,
|
||||
Num7 = SDL_SCANCODE_7,
|
||||
Num8 = SDL_SCANCODE_8,
|
||||
Num9 = SDL_SCANCODE_9,
|
||||
|
||||
// 功能键
|
||||
F1 = SDL_SCANCODE_F1,
|
||||
F2 = SDL_SCANCODE_F2,
|
||||
F3 = SDL_SCANCODE_F3,
|
||||
F4 = SDL_SCANCODE_F4,
|
||||
F5 = SDL_SCANCODE_F5,
|
||||
F6 = SDL_SCANCODE_F6,
|
||||
F7 = SDL_SCANCODE_F7,
|
||||
F8 = SDL_SCANCODE_F8,
|
||||
F9 = SDL_SCANCODE_F9,
|
||||
F10 = SDL_SCANCODE_F10,
|
||||
F11 = SDL_SCANCODE_F11,
|
||||
F12 = SDL_SCANCODE_F12,
|
||||
|
||||
// 特殊键
|
||||
Space = SDL_SCANCODE_SPACE,
|
||||
Enter = SDL_SCANCODE_RETURN,
|
||||
Escape = SDL_SCANCODE_ESCAPE,
|
||||
Tab = SDL_SCANCODE_TAB,
|
||||
Backspace = SDL_SCANCODE_BACKSPACE,
|
||||
Insert = SDL_SCANCODE_INSERT,
|
||||
Delete = SDL_SCANCODE_DELETE,
|
||||
Home = SDL_SCANCODE_HOME,
|
||||
End = SDL_SCANCODE_END,
|
||||
PageUp = SDL_SCANCODE_PAGEUP,
|
||||
PageDown = SDL_SCANCODE_PAGEDOWN,
|
||||
|
||||
// 方向键
|
||||
Up = SDL_SCANCODE_UP,
|
||||
Down = SDL_SCANCODE_DOWN,
|
||||
Left = SDL_SCANCODE_LEFT,
|
||||
Right = SDL_SCANCODE_RIGHT,
|
||||
|
||||
// 修饰键
|
||||
LShift = SDL_SCANCODE_LSHIFT,
|
||||
RShift = SDL_SCANCODE_RSHIFT,
|
||||
LCtrl = SDL_SCANCODE_LCTRL,
|
||||
RCtrl = SDL_SCANCODE_RCTRL,
|
||||
LAlt = SDL_SCANCODE_LALT,
|
||||
RAlt = SDL_SCANCODE_RALT,
|
||||
|
||||
// 锁定键
|
||||
CapsLock = SDL_SCANCODE_CAPSLOCK,
|
||||
NumLock = SDL_SCANCODE_NUMLOCKCLEAR,
|
||||
ScrollLock = SDL_SCANCODE_SCROLLLOCK,
|
||||
|
||||
// 小键盘
|
||||
KP0 = SDL_SCANCODE_KP_0,
|
||||
KP1 = SDL_SCANCODE_KP_1,
|
||||
KP2 = SDL_SCANCODE_KP_2,
|
||||
KP3 = SDL_SCANCODE_KP_3,
|
||||
KP4 = SDL_SCANCODE_KP_4,
|
||||
KP5 = SDL_SCANCODE_KP_5,
|
||||
KP6 = SDL_SCANCODE_KP_6,
|
||||
KP7 = SDL_SCANCODE_KP_7,
|
||||
KP8 = SDL_SCANCODE_KP_8,
|
||||
KP9 = SDL_SCANCODE_KP_9,
|
||||
KPPlus = SDL_SCANCODE_KP_PLUS,
|
||||
KPMinus = SDL_SCANCODE_KP_MINUS,
|
||||
KPMultiply = SDL_SCANCODE_KP_MULTIPLY,
|
||||
KPDivide = SDL_SCANCODE_KP_DIVIDE,
|
||||
KPEnter = SDL_SCANCODE_KP_ENTER,
|
||||
KPPeriod = SDL_SCANCODE_KP_PERIOD,
|
||||
|
||||
// 符号键
|
||||
Minus = SDL_SCANCODE_MINUS,
|
||||
Equals = SDL_SCANCODE_EQUALS,
|
||||
LeftBracket = SDL_SCANCODE_LEFTBRACKET,
|
||||
RightBracket = SDL_SCANCODE_RIGHTBRACKET,
|
||||
Backslash = SDL_SCANCODE_BACKSLASH,
|
||||
Semicolon = SDL_SCANCODE_SEMICOLON,
|
||||
Apostrophe = SDL_SCANCODE_APOSTROPHE,
|
||||
Grave = SDL_SCANCODE_GRAVE,
|
||||
Comma = SDL_SCANCODE_COMMA,
|
||||
Period = SDL_SCANCODE_PERIOD,
|
||||
Slash = SDL_SCANCODE_SLASH,
|
||||
|
||||
// 其他
|
||||
PrintScreen = SDL_SCANCODE_PRINTSCREEN,
|
||||
Pause = SDL_SCANCODE_PAUSE,
|
||||
|
||||
Count = SDL_NUM_SCANCODES
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 鼠标按钮
|
||||
* @brief 鼠标按钮(跨平台统一)
|
||||
*
|
||||
* 使用 SDL 鼠标按钮定义
|
||||
*/
|
||||
enum class Mouse : int {
|
||||
Left = 0,
|
||||
Right,
|
||||
Middle,
|
||||
X1,
|
||||
X2,
|
||||
Count
|
||||
Left = SDL_BUTTON_LEFT,
|
||||
Middle = SDL_BUTTON_MIDDLE,
|
||||
Right = SDL_BUTTON_RIGHT,
|
||||
X1 = SDL_BUTTON_X1,
|
||||
X2 = SDL_BUTTON_X2,
|
||||
Count
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 游戏手柄按钮
|
||||
* @brief 游戏手柄按钮(跨平台统一)
|
||||
*
|
||||
* 使用 SDL GameController 按钮定义
|
||||
*/
|
||||
enum class Gamepad : int {
|
||||
A = 0,
|
||||
B,
|
||||
X,
|
||||
Y,
|
||||
LB,
|
||||
RB,
|
||||
LT,
|
||||
RT,
|
||||
Back,
|
||||
Start,
|
||||
Guide,
|
||||
LStick,
|
||||
RStick,
|
||||
DUp,
|
||||
DDown,
|
||||
DLeft,
|
||||
DRight,
|
||||
Count
|
||||
A = SDL_CONTROLLER_BUTTON_A,
|
||||
B = SDL_CONTROLLER_BUTTON_B,
|
||||
X = SDL_CONTROLLER_BUTTON_X,
|
||||
Y = SDL_CONTROLLER_BUTTON_Y,
|
||||
Back = SDL_CONTROLLER_BUTTON_BACK,
|
||||
Guide = SDL_CONTROLLER_BUTTON_GUIDE,
|
||||
Start = SDL_CONTROLLER_BUTTON_START,
|
||||
LStick = SDL_CONTROLLER_BUTTON_LEFTSTICK,
|
||||
RStick = SDL_CONTROLLER_BUTTON_RIGHTSTICK,
|
||||
LB = SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
|
||||
RB = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
|
||||
DUp = SDL_CONTROLLER_BUTTON_DPAD_UP,
|
||||
DDown = SDL_CONTROLLER_BUTTON_DPAD_DOWN,
|
||||
DLeft = SDL_CONTROLLER_BUTTON_DPAD_LEFT,
|
||||
DRight = SDL_CONTROLLER_BUTTON_DPAD_RIGHT,
|
||||
LT = SDL_CONTROLLER_BUTTON_MAX, // 轴映射
|
||||
RT, // 轴映射
|
||||
Count
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 手柄轴
|
||||
* @brief 手柄轴(跨平台统一)
|
||||
*
|
||||
* 使用 SDL GameController 轴定义
|
||||
*/
|
||||
enum class GamepadAxis : int {
|
||||
LeftX = 0,
|
||||
LeftY,
|
||||
RightX,
|
||||
RightY,
|
||||
LeftTrigger,
|
||||
RightTrigger,
|
||||
Count
|
||||
LeftX = SDL_CONTROLLER_AXIS_LEFTX,
|
||||
LeftY = SDL_CONTROLLER_AXIS_LEFTY,
|
||||
RightX = SDL_CONTROLLER_AXIS_RIGHTX,
|
||||
RightY = SDL_CONTROLLER_AXIS_RIGHTY,
|
||||
LeftTrigger = SDL_CONTROLLER_AXIS_TRIGGERLEFT,
|
||||
RightTrigger = SDL_CONTROLLER_AXIS_TRIGGERRIGHT,
|
||||
Count
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,67 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/platform/window_config.h>
|
||||
#include <extra2d/platform/iwindow.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 窗口模块
|
||||
* 管理窗口创建和生命周期
|
||||
*/
|
||||
class WindowModule : public Module {
|
||||
public:
|
||||
/**
|
||||
* @brief 配置结构
|
||||
*/
|
||||
struct Cfg {
|
||||
std::string title;
|
||||
int w;
|
||||
int h;
|
||||
WindowMode mode;
|
||||
bool vsync;
|
||||
int priority;
|
||||
std::string backend;
|
||||
|
||||
Cfg()
|
||||
: title("Extra2D")
|
||||
, w(1280)
|
||||
, h(720)
|
||||
, mode(WindowMode::Windowed)
|
||||
, vsync(true)
|
||||
, priority(0)
|
||||
, backend("sdl2") {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param cfg 配置
|
||||
*/
|
||||
explicit WindowModule(const Cfg& cfg = Cfg());
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~WindowModule() override;
|
||||
|
||||
bool init() override;
|
||||
void shutdown() override;
|
||||
bool ok() const override { return initialized_; }
|
||||
const char* name() const override { return "window"; }
|
||||
int priority() const override { return cfg_.priority; }
|
||||
|
||||
/**
|
||||
* @brief 获取窗口
|
||||
* @return 窗口指针
|
||||
*/
|
||||
IWindow* win() const { return win_.get(); }
|
||||
|
||||
private:
|
||||
Cfg cfg_;
|
||||
UniquePtr<IWindow> win_;
|
||||
bool initialized_ = false;
|
||||
bool sdlInited_ = false;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/core/export.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/event/event_dispatcher.h>
|
||||
|
|
@ -19,7 +20,7 @@ struct RenderCommand;
|
|||
// ============================================================================
|
||||
// 节点基类 - 场景图的基础
|
||||
// ============================================================================
|
||||
class Node : public std::enable_shared_from_this<Node> {
|
||||
class E2D_API Node : public std::enable_shared_from_this<Node> {
|
||||
public:
|
||||
Node();
|
||||
virtual ~Node();
|
||||
|
|
@ -151,6 +152,31 @@ public:
|
|||
// ------------------------------------------------------------------------
|
||||
EventDispatcher &getEventDispatcher() { return eventDispatcher_; }
|
||||
|
||||
/**
|
||||
* @brief 添加事件监听器(便捷方法)
|
||||
* @param type 事件类型
|
||||
* @param callback 回调函数
|
||||
* @return 监听器ID
|
||||
*/
|
||||
ListenerId addListener(EventType type, EventDispatcher::EventCallback callback);
|
||||
|
||||
/**
|
||||
* @brief 移除事件监听器
|
||||
* @param id 监听器ID
|
||||
*/
|
||||
void removeListener(ListenerId id);
|
||||
|
||||
/**
|
||||
* @brief 移除指定类型的所有监听器
|
||||
* @param type 事件类型
|
||||
*/
|
||||
void removeAllListeners(EventType type);
|
||||
|
||||
/**
|
||||
* @brief 移除所有监听器
|
||||
*/
|
||||
void removeAllListeners();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 内部方法
|
||||
// ------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/core/export.h>
|
||||
#include <extra2d/core/math_types.h>
|
||||
#include <extra2d/scene/node.h>
|
||||
#include <vector>
|
||||
|
|
@ -15,7 +16,7 @@ enum class ShapeType { Point, Line, Rect, Circle, Triangle, Polygon };
|
|||
// ============================================================================
|
||||
// 形状节点 - 用于绘制几何形状
|
||||
// ============================================================================
|
||||
class ShapeNode : public Node {
|
||||
class E2D_API ShapeNode : public Node {
|
||||
public:
|
||||
ShapeNode();
|
||||
~ShapeNode() override = default;
|
||||
|
|
|
|||
|
|
@ -60,11 +60,6 @@ public:
|
|||
size_t getTotalListenerCount() const override;
|
||||
size_t getQueueSize() const override;
|
||||
|
||||
EventQueue& getQueue() { return queue_; }
|
||||
const EventQueue& getQueue() const { return queue_; }
|
||||
EventDispatcher& getDispatcher() { return dispatcher_; }
|
||||
const EventDispatcher& getDispatcher() const { return dispatcher_; }
|
||||
|
||||
private:
|
||||
EventQueue queue_;
|
||||
EventDispatcher dispatcher_;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ AppConfig AppConfig::createDefault() {
|
|||
config.appVersion = "1.0.0";
|
||||
config.organization = "";
|
||||
config.configFile = "config.json";
|
||||
config.targetPlatform = PlatformType::Auto;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
|
@ -50,9 +49,6 @@ void AppConfig::merge(const AppConfig& other) {
|
|||
if (other.configFile != "config.json") {
|
||||
configFile = other.configFile;
|
||||
}
|
||||
if (other.targetPlatform != PlatformType::Auto) {
|
||||
targetPlatform = other.targetPlatform;
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Merged app config");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,224 +0,0 @@
|
|||
#include <extra2d/config/app_config.h>
|
||||
#include <extra2d/config/platform_config.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#ifdef __SWITCH__
|
||||
#include <switch.h>
|
||||
#endif
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
namespace {
|
||||
|
||||
class WindowsPlatformConfig : public PlatformConfig {
|
||||
public:
|
||||
WindowsPlatformConfig() {
|
||||
caps_.supportsWindowed = true;
|
||||
caps_.supportsFullscreen = true;
|
||||
caps_.supportsBorderless = true;
|
||||
caps_.supportsCursor = true;
|
||||
caps_.supportsCursorHide = true;
|
||||
caps_.supportsDPIAwareness = true;
|
||||
caps_.supportsVSync = true;
|
||||
caps_.supportsMultiMonitor = true;
|
||||
caps_.supportsClipboard = true;
|
||||
caps_.supportsGamepad = true;
|
||||
caps_.supportsTouch = false;
|
||||
caps_.supportsKeyboard = true;
|
||||
caps_.supportsMouse = true;
|
||||
caps_.supportsResize = true;
|
||||
caps_.supportsHighDPI = true;
|
||||
caps_.maxTextureSize = 16384;
|
||||
caps_.preferredScreenWidth = 1920;
|
||||
caps_.preferredScreenHeight = 1080;
|
||||
caps_.defaultDPI = 96.0f;
|
||||
}
|
||||
|
||||
PlatformType platformType() const override { return PlatformType::Windows; }
|
||||
const char *platformName() const override { return "Windows"; }
|
||||
const PlatformCapabilities &capabilities() const override { return caps_; }
|
||||
|
||||
int getRecommendedWidth() const override { return 1920; }
|
||||
int getRecommendedHeight() const override { return 1080; }
|
||||
bool isResolutionSupported(int width, int height) const override {
|
||||
return width >= 320 && height >= 240 && width <= caps_.maxTextureSize &&
|
||||
height <= caps_.maxTextureSize;
|
||||
}
|
||||
|
||||
private:
|
||||
PlatformCapabilities caps_;
|
||||
};
|
||||
|
||||
class LinuxPlatformConfig : public PlatformConfig {
|
||||
public:
|
||||
LinuxPlatformConfig() {
|
||||
caps_.supportsWindowed = true;
|
||||
caps_.supportsFullscreen = true;
|
||||
caps_.supportsBorderless = true;
|
||||
caps_.supportsCursor = true;
|
||||
caps_.supportsCursorHide = true;
|
||||
caps_.supportsDPIAwareness = true;
|
||||
caps_.supportsVSync = true;
|
||||
caps_.supportsMultiMonitor = true;
|
||||
caps_.supportsClipboard = true;
|
||||
caps_.supportsGamepad = true;
|
||||
caps_.supportsTouch = false;
|
||||
caps_.supportsKeyboard = true;
|
||||
caps_.supportsMouse = true;
|
||||
caps_.supportsResize = true;
|
||||
caps_.supportsHighDPI = true;
|
||||
caps_.maxTextureSize = 16384;
|
||||
caps_.preferredScreenWidth = 1920;
|
||||
caps_.preferredScreenHeight = 1080;
|
||||
caps_.defaultDPI = 96.0f;
|
||||
}
|
||||
|
||||
PlatformType platformType() const override { return PlatformType::Linux; }
|
||||
const char *platformName() const override { return "Linux"; }
|
||||
const PlatformCapabilities &capabilities() const override { return caps_; }
|
||||
|
||||
int getRecommendedWidth() const override { return 1920; }
|
||||
int getRecommendedHeight() const override { return 1080; }
|
||||
bool isResolutionSupported(int width, int height) const override {
|
||||
return width >= 320 && height >= 240;
|
||||
}
|
||||
|
||||
private:
|
||||
PlatformCapabilities caps_;
|
||||
};
|
||||
|
||||
class MacOSPlatformConfig : public PlatformConfig {
|
||||
public:
|
||||
MacOSPlatformConfig() {
|
||||
caps_.supportsWindowed = true;
|
||||
caps_.supportsFullscreen = true;
|
||||
caps_.supportsBorderless = true;
|
||||
caps_.supportsCursor = true;
|
||||
caps_.supportsCursorHide = true;
|
||||
caps_.supportsDPIAwareness = true;
|
||||
caps_.supportsVSync = true;
|
||||
caps_.supportsMultiMonitor = true;
|
||||
caps_.supportsClipboard = true;
|
||||
caps_.supportsGamepad = true;
|
||||
caps_.supportsTouch = false;
|
||||
caps_.supportsKeyboard = true;
|
||||
caps_.supportsMouse = true;
|
||||
caps_.supportsResize = true;
|
||||
caps_.supportsHighDPI = true;
|
||||
caps_.maxTextureSize = 16384;
|
||||
caps_.preferredScreenWidth = 1920;
|
||||
caps_.preferredScreenHeight = 1080;
|
||||
caps_.defaultDPI = 144.0f;
|
||||
}
|
||||
|
||||
PlatformType platformType() const override { return PlatformType::macOS; }
|
||||
const char *platformName() const override { return "macOS"; }
|
||||
const PlatformCapabilities &capabilities() const override { return caps_; }
|
||||
|
||||
int getRecommendedWidth() const override { return 1920; }
|
||||
int getRecommendedHeight() const override { return 1080; }
|
||||
bool isResolutionSupported(int width, int height) const override {
|
||||
return width >= 320 && height >= 240;
|
||||
}
|
||||
|
||||
private:
|
||||
PlatformCapabilities caps_;
|
||||
};
|
||||
|
||||
class SwitchPlatformConfig : public PlatformConfig {
|
||||
public:
|
||||
SwitchPlatformConfig() {
|
||||
caps_.supportsWindowed = false;
|
||||
caps_.supportsFullscreen = true;
|
||||
caps_.supportsBorderless = false;
|
||||
caps_.supportsCursor = false;
|
||||
caps_.supportsCursorHide = false;
|
||||
caps_.supportsDPIAwareness = false;
|
||||
caps_.supportsVSync = true;
|
||||
caps_.supportsMultiMonitor = false;
|
||||
caps_.supportsClipboard = false;
|
||||
caps_.supportsGamepad = true;
|
||||
caps_.supportsTouch = true;
|
||||
caps_.supportsKeyboard = false;
|
||||
caps_.supportsMouse = false;
|
||||
caps_.supportsResize = false;
|
||||
caps_.supportsHighDPI = false;
|
||||
caps_.maxTextureSize = 8192;
|
||||
caps_.preferredScreenWidth = 1920;
|
||||
caps_.preferredScreenHeight = 1080;
|
||||
caps_.defaultDPI = 96.0f;
|
||||
}
|
||||
|
||||
PlatformType platformType() const override { return PlatformType::Switch; }
|
||||
const char *platformName() const override { return "Nintendo Switch"; }
|
||||
const PlatformCapabilities &capabilities() const override { return caps_; }
|
||||
|
||||
int getRecommendedWidth() const override { return 1920; }
|
||||
int getRecommendedHeight() const override { return 1080; }
|
||||
bool isResolutionSupported(int width, int height) const override {
|
||||
return (width == 1920 && height == 1080) ||
|
||||
(width == 1280 && height == 720);
|
||||
}
|
||||
|
||||
private:
|
||||
PlatformCapabilities caps_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
UniquePtr<PlatformConfig> createPlatformConfig(PlatformType type) {
|
||||
if (type == PlatformType::Auto) {
|
||||
#ifdef _WIN32
|
||||
type = PlatformType::Windows;
|
||||
#elif defined(__SWITCH__)
|
||||
type = PlatformType::Switch;
|
||||
#elif defined(__linux__)
|
||||
type = PlatformType::Linux;
|
||||
#elif defined(__APPLE__)
|
||||
type = PlatformType::macOS;
|
||||
#else
|
||||
type = PlatformType::Windows;
|
||||
#endif
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case PlatformType::Windows:
|
||||
E2D_LOG_INFO("Creating Windows platform config");
|
||||
return makeUnique<WindowsPlatformConfig>();
|
||||
case PlatformType::Switch:
|
||||
E2D_LOG_INFO("Creating Nintendo Switch platform config");
|
||||
return makeUnique<SwitchPlatformConfig>();
|
||||
case PlatformType::Linux:
|
||||
E2D_LOG_INFO("Creating Linux platform config");
|
||||
return makeUnique<LinuxPlatformConfig>();
|
||||
case PlatformType::macOS:
|
||||
E2D_LOG_INFO("Creating macOS platform config");
|
||||
return makeUnique<MacOSPlatformConfig>();
|
||||
default:
|
||||
E2D_LOG_WARN("Unknown platform type, defaulting to Windows");
|
||||
return makeUnique<WindowsPlatformConfig>();
|
||||
}
|
||||
}
|
||||
|
||||
const char *getPlatformTypeName(PlatformType type) {
|
||||
switch (type) {
|
||||
case PlatformType::Auto:
|
||||
return "Auto";
|
||||
case PlatformType::Windows:
|
||||
return "Windows";
|
||||
case PlatformType::Switch:
|
||||
return "Switch";
|
||||
case PlatformType::Linux:
|
||||
return "Linux";
|
||||
case PlatformType::macOS:
|
||||
return "macOS";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,678 +0,0 @@
|
|||
#include <extra2d/config/platform_detector.h>
|
||||
#include <extra2d/config/platform_config.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <shlobj.h>
|
||||
#include <psapi.h>
|
||||
#elif defined(__linux__)
|
||||
#include <sys/sysinfo.h>
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
#include <cstdlib>
|
||||
#elif defined(__APPLE__)
|
||||
#include <sys/sysctl.h>
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
#include <cstdlib>
|
||||
#endif
|
||||
|
||||
#ifdef __SWITCH__
|
||||
#include <switch.h>
|
||||
#endif
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 检测当前运行平台
|
||||
* 使用编译时宏判断当前平台类型
|
||||
* @return 当前平台的类型枚举值
|
||||
*/
|
||||
PlatformType PlatformDetector::detect() {
|
||||
#ifdef _WIN32
|
||||
return PlatformType::Windows;
|
||||
#elif defined(__SWITCH__)
|
||||
return PlatformType::Switch;
|
||||
#elif defined(__linux__)
|
||||
return PlatformType::Linux;
|
||||
#elif defined(__APPLE__)
|
||||
return PlatformType::macOS;
|
||||
#else
|
||||
return PlatformType::Windows;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取当前平台名称字符串
|
||||
* @return 平台名称(如 "Windows", "Linux", "macOS", "Switch")
|
||||
*/
|
||||
const char* PlatformDetector::platformName() {
|
||||
return platformName(detect());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取指定平台类型的名称
|
||||
* @param type 平台类型
|
||||
* @return 平台名称字符串
|
||||
*/
|
||||
const char* PlatformDetector::platformName(PlatformType type) {
|
||||
switch (type) {
|
||||
case PlatformType::Windows: return "Windows";
|
||||
case PlatformType::Switch: return "Nintendo Switch";
|
||||
case PlatformType::Linux: return "Linux";
|
||||
case PlatformType::macOS: return "macOS";
|
||||
case PlatformType::Auto: return "Auto";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查当前平台是否为桌面平台
|
||||
* @return 如果是桌面平台返回 true
|
||||
*/
|
||||
bool PlatformDetector::isDesktopPlatform() {
|
||||
PlatformType type = detect();
|
||||
return type == PlatformType::Windows ||
|
||||
type == PlatformType::Linux ||
|
||||
type == PlatformType::macOS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查当前平台是否为游戏主机平台
|
||||
* @return 如果是游戏主机平台返回 true
|
||||
*/
|
||||
bool PlatformDetector::isConsolePlatform() {
|
||||
return detect() == PlatformType::Switch;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查当前平台是否为移动平台
|
||||
* @return 如果是移动平台返回 true
|
||||
*/
|
||||
bool PlatformDetector::isMobilePlatform() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取当前平台的能力
|
||||
* @return 平台能力结构
|
||||
*/
|
||||
PlatformCapabilities PlatformDetector::capabilities() {
|
||||
return capabilities(detect());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取指定平台的能力
|
||||
* @param type 平台类型
|
||||
* @return 平台能力结构
|
||||
*/
|
||||
PlatformCapabilities PlatformDetector::capabilities(PlatformType type) {
|
||||
switch (type) {
|
||||
case PlatformType::Windows:
|
||||
return getWindowsCapabilities();
|
||||
case PlatformType::Switch:
|
||||
return getSwitchCapabilities();
|
||||
case PlatformType::Linux:
|
||||
return getLinuxCapabilities();
|
||||
case PlatformType::macOS:
|
||||
return getMacOSCapabilities();
|
||||
default:
|
||||
return getWindowsCapabilities();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取当前平台的默认配置
|
||||
* @return 平台默认的应用配置
|
||||
*/
|
||||
AppConfig PlatformDetector::platformDefaults() {
|
||||
return platformDefaults(detect());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取指定平台的默认配置
|
||||
* @param type 平台类型
|
||||
* @return 平台默认的应用配置
|
||||
*/
|
||||
AppConfig PlatformDetector::platformDefaults(PlatformType type) {
|
||||
switch (type) {
|
||||
case PlatformType::Windows:
|
||||
return getWindowsDefaults();
|
||||
case PlatformType::Switch:
|
||||
return getSwitchDefaults();
|
||||
case PlatformType::Linux:
|
||||
return getLinuxDefaults();
|
||||
case PlatformType::macOS:
|
||||
return getMacOSDefaults();
|
||||
default:
|
||||
return AppConfig::createDefault();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取当前平台的推荐分辨率
|
||||
* @param width 输出宽度
|
||||
* @param height 输出高度
|
||||
*/
|
||||
void PlatformDetector::getRecommendedResolution(int& width, int& height) {
|
||||
PlatformCapabilities caps = capabilities();
|
||||
width = caps.preferredScreenWidth;
|
||||
height = caps.preferredScreenHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取当前平台的默认 DPI
|
||||
* @return 默认 DPI 值
|
||||
*/
|
||||
float PlatformDetector::getDefaultDPI() {
|
||||
return capabilities().defaultDPI;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查当前平台是否支持指定功能
|
||||
* @param feature 功能名称
|
||||
* @return 如果支持返回 true
|
||||
*/
|
||||
bool PlatformDetector::supportsFeature(const std::string& feature) {
|
||||
PlatformCapabilities caps = capabilities();
|
||||
|
||||
if (feature == "windowed") return caps.supportsWindowed;
|
||||
if (feature == "fullscreen") return caps.supportsFullscreen;
|
||||
if (feature == "borderless") return caps.supportsBorderless;
|
||||
if (feature == "cursor") return caps.supportsCursor;
|
||||
if (feature == "cursor_hide") return caps.supportsCursorHide;
|
||||
if (feature == "dpi_awareness") return caps.supportsDPIAwareness;
|
||||
if (feature == "vsync") return caps.supportsVSync;
|
||||
if (feature == "multi_monitor") return caps.supportsMultiMonitor;
|
||||
if (feature == "clipboard") return caps.supportsClipboard;
|
||||
if (feature == "gamepad") return caps.supportsGamepad;
|
||||
if (feature == "touch") return caps.supportsTouch;
|
||||
if (feature == "keyboard") return caps.supportsKeyboard;
|
||||
if (feature == "mouse") return caps.supportsMouse;
|
||||
if (feature == "resize") return caps.supportsResize;
|
||||
if (feature == "high_dpi") return caps.supportsHighDPI;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取系统内存大小
|
||||
* @return 系统内存大小(MB),如果无法获取返回 0
|
||||
*/
|
||||
int PlatformDetector::getSystemMemoryMB() {
|
||||
#ifdef _WIN32
|
||||
MEMORYSTATUSEX status;
|
||||
status.dwLength = sizeof(status);
|
||||
if (GlobalMemoryStatusEx(&status)) {
|
||||
return static_cast<int>(status.ullTotalPhys / (1024 * 1024));
|
||||
}
|
||||
return 0;
|
||||
#elif defined(__SWITCH__)
|
||||
return 4096;
|
||||
#elif defined(__linux__)
|
||||
struct sysinfo info;
|
||||
if (sysinfo(&info) == 0) {
|
||||
return static_cast<int>(info.totalram * info.mem_unit / (1024 * 1024));
|
||||
}
|
||||
return 0;
|
||||
#elif defined(__APPLE__)
|
||||
int mib[2] = {CTL_HW, HW_MEMSIZE};
|
||||
int64_t memSize = 0;
|
||||
size_t length = sizeof(memSize);
|
||||
if (sysctl(mib, 2, &memSize, &length, nullptr, 0) == 0) {
|
||||
return static_cast<int>(memSize / (1024 * 1024));
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取 CPU 核心数
|
||||
* @return CPU 核心数
|
||||
*/
|
||||
int PlatformDetector::getCPUCoreCount() {
|
||||
#ifdef _WIN32
|
||||
SYSTEM_INFO sysinfo;
|
||||
GetSystemInfo(&sysinfo);
|
||||
return static_cast<int>(sysinfo.dwNumberOfProcessors);
|
||||
#elif defined(__SWITCH__)
|
||||
return 4;
|
||||
#elif defined(__linux__) || defined(__APPLE__)
|
||||
long cores = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
return static_cast<int>(cores > 0 ? cores : 1);
|
||||
#else
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查是否支持多线程渲染
|
||||
* @return 如果支持返回 true
|
||||
*/
|
||||
bool PlatformDetector::supportsMultithreadedRendering() {
|
||||
#ifdef __SWITCH__
|
||||
return false;
|
||||
#else
|
||||
return getCPUCoreCount() >= 2;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取平台特定的配置路径
|
||||
* @param appName 应用名称
|
||||
* @return 配置文件目录路径
|
||||
*/
|
||||
std::string PlatformDetector::getConfigPath(const std::string& appName) {
|
||||
#ifdef _WIN32
|
||||
char path[MAX_PATH];
|
||||
if (SUCCEEDED(SHGetFolderPathA(nullptr, CSIDL_APPDATA, nullptr, 0, path))) {
|
||||
return std::string(path) + "\\" + appName + "\\config";
|
||||
}
|
||||
return ".\\config";
|
||||
#elif defined(__SWITCH__)
|
||||
return "sdmc:/config/" + appName;
|
||||
#elif defined(__linux__)
|
||||
const char* configHome = getenv("XDG_CONFIG_HOME");
|
||||
if (configHome && configHome[0] != '\0') {
|
||||
return std::string(configHome) + "/" + appName;
|
||||
}
|
||||
const char* home = getenv("HOME");
|
||||
if (!home) {
|
||||
struct passwd* pwd = getpwuid(getuid());
|
||||
if (pwd) home = pwd->pw_dir;
|
||||
}
|
||||
if (home) {
|
||||
return std::string(home) + "/.config/" + appName;
|
||||
}
|
||||
return "./config";
|
||||
#elif defined(__APPLE__)
|
||||
const char* home = getenv("HOME");
|
||||
if (!home) {
|
||||
struct passwd* pwd = getpwuid(getuid());
|
||||
if (pwd) home = pwd->pw_dir;
|
||||
}
|
||||
if (home) {
|
||||
return std::string(home) + "/Library/Application Support/" + appName + "/config";
|
||||
}
|
||||
return "./config";
|
||||
#else
|
||||
return "./config";
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取平台特定的存档路径
|
||||
* @param appName 应用名称
|
||||
* @return 存档文件目录路径
|
||||
*/
|
||||
std::string PlatformDetector::getSavePath(const std::string& appName) {
|
||||
#ifdef _WIN32
|
||||
char path[MAX_PATH];
|
||||
if (SUCCEEDED(SHGetFolderPathA(nullptr, CSIDL_APPDATA, nullptr, 0, path))) {
|
||||
return std::string(path) + "\\" + appName + "\\saves";
|
||||
}
|
||||
return ".\\saves";
|
||||
#elif defined(__SWITCH__)
|
||||
return "sdmc:/saves/" + appName;
|
||||
#elif defined(__linux__)
|
||||
const char* dataHome = getenv("XDG_DATA_HOME");
|
||||
if (dataHome && dataHome[0] != '\0') {
|
||||
return std::string(dataHome) + "/" + appName + "/saves";
|
||||
}
|
||||
const char* home = getenv("HOME");
|
||||
if (!home) {
|
||||
struct passwd* pwd = getpwuid(getuid());
|
||||
if (pwd) home = pwd->pw_dir;
|
||||
}
|
||||
if (home) {
|
||||
return std::string(home) + "/.local/share/" + appName + "/saves";
|
||||
}
|
||||
return "./saves";
|
||||
#elif defined(__APPLE__)
|
||||
const char* home = getenv("HOME");
|
||||
if (!home) {
|
||||
struct passwd* pwd = getpwuid(getuid());
|
||||
if (pwd) home = pwd->pw_dir;
|
||||
}
|
||||
if (home) {
|
||||
return std::string(home) + "/Library/Application Support/" + appName + "/saves";
|
||||
}
|
||||
return "./saves";
|
||||
#else
|
||||
return "./saves";
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取平台特定的缓存路径
|
||||
* @param appName 应用名称
|
||||
* @return 缓存文件目录路径
|
||||
*/
|
||||
std::string PlatformDetector::getCachePath(const std::string& appName) {
|
||||
#ifdef _WIN32
|
||||
char path[MAX_PATH];
|
||||
if (SUCCEEDED(SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, path))) {
|
||||
return std::string(path) + "\\" + appName + "\\cache";
|
||||
}
|
||||
return ".\\cache";
|
||||
#elif defined(__SWITCH__)
|
||||
return "sdmc:/cache/" + appName;
|
||||
#elif defined(__linux__)
|
||||
const char* cacheHome = getenv("XDG_CACHE_HOME");
|
||||
if (cacheHome && cacheHome[0] != '\0') {
|
||||
return std::string(cacheHome) + "/" + appName;
|
||||
}
|
||||
const char* home = getenv("HOME");
|
||||
if (!home) {
|
||||
struct passwd* pwd = getpwuid(getuid());
|
||||
if (pwd) home = pwd->pw_dir;
|
||||
}
|
||||
if (home) {
|
||||
return std::string(home) + "/.cache/" + appName;
|
||||
}
|
||||
return "./cache";
|
||||
#elif defined(__APPLE__)
|
||||
const char* home = getenv("HOME");
|
||||
if (!home) {
|
||||
struct passwd* pwd = getpwuid(getuid());
|
||||
if (pwd) home = pwd->pw_dir;
|
||||
}
|
||||
if (home) {
|
||||
return std::string(home) + "/Library/Caches/" + appName;
|
||||
}
|
||||
return "./cache";
|
||||
#else
|
||||
return "./cache";
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取平台特定的日志路径
|
||||
* @param appName 应用名称
|
||||
* @return 日志文件目录路径
|
||||
*/
|
||||
std::string PlatformDetector::getLogPath(const std::string& appName) {
|
||||
#ifdef _WIN32
|
||||
char path[MAX_PATH];
|
||||
if (SUCCEEDED(SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, path))) {
|
||||
return std::string(path) + "\\" + appName + "\\logs";
|
||||
}
|
||||
return ".\\logs";
|
||||
#elif defined(__SWITCH__)
|
||||
return "sdmc:/logs/" + appName;
|
||||
#elif defined(__linux__)
|
||||
const char* cacheHome = getenv("XDG_CACHE_HOME");
|
||||
if (cacheHome && cacheHome[0] != '\0') {
|
||||
return std::string(cacheHome) + "/" + appName + "/logs";
|
||||
}
|
||||
const char* home = getenv("HOME");
|
||||
if (!home) {
|
||||
struct passwd* pwd = getpwuid(getuid());
|
||||
if (pwd) home = pwd->pw_dir;
|
||||
}
|
||||
if (home) {
|
||||
return std::string(home) + "/.cache/" + appName + "/logs";
|
||||
}
|
||||
return "./logs";
|
||||
#elif defined(__APPLE__)
|
||||
const char* home = getenv("HOME");
|
||||
if (!home) {
|
||||
struct passwd* pwd = getpwuid(getuid());
|
||||
if (pwd) home = pwd->pw_dir;
|
||||
}
|
||||
if (home) {
|
||||
return std::string(home) + "/Library/Logs/" + appName;
|
||||
}
|
||||
return "./logs";
|
||||
#else
|
||||
return "./logs";
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取平台特定的资源路径(Shader、纹理等)
|
||||
* Switch平台使用romfs,其他平台使用相对路径
|
||||
* @param appName 应用名称
|
||||
* @return 资源目录路径
|
||||
*/
|
||||
std::string PlatformDetector::getResourcePath(const std::string& appName) {
|
||||
#ifdef __SWITCH__
|
||||
(void)appName;
|
||||
return "romfs:/";
|
||||
#else
|
||||
(void)appName;
|
||||
return "./resources/";
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取平台特定的Shader路径
|
||||
* @param appName 应用名称
|
||||
* @return Shader目录路径
|
||||
*/
|
||||
std::string PlatformDetector::getShaderPath(const std::string& appName) {
|
||||
#ifdef __SWITCH__
|
||||
(void)appName;
|
||||
return "romfs:/shaders/";
|
||||
#else
|
||||
(void)appName;
|
||||
return "./shaders/";
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取平台特定的Shader缓存路径
|
||||
* Switch平台使用sdmc,其他平台使用系统缓存目录
|
||||
* @param appName 应用名称
|
||||
* @return Shader缓存目录路径
|
||||
*/
|
||||
std::string PlatformDetector::getShaderCachePath(const std::string& appName) {
|
||||
#ifdef __SWITCH__
|
||||
std::string name = appName.empty() ? "extra2d" : appName;
|
||||
return "sdmc:/cache/" + name + "/shaders/";
|
||||
#else
|
||||
return getCachePath(appName.empty() ? "extra2d" : appName) + "/shaders/";
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查平台是否使用romfs(只读文件系统)
|
||||
* @return 使用romfs返回true
|
||||
*/
|
||||
bool PlatformDetector::usesRomfs() {
|
||||
#ifdef __SWITCH__
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查平台是否支持热重载
|
||||
* Switch平台不支持热重载(romfs只读)
|
||||
* @return 支持热重载返回true
|
||||
*/
|
||||
bool PlatformDetector::supportsHotReload() {
|
||||
#ifdef __SWITCH__
|
||||
return false;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查平台是否为小端字节序
|
||||
* @return 如果是小端字节序返回 true
|
||||
*/
|
||||
bool PlatformDetector::isLittleEndian() {
|
||||
union {
|
||||
uint32_t i;
|
||||
char c[4];
|
||||
} test = {0x01020304};
|
||||
return test.c[0] == 0x04;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查平台是否为大端字节序
|
||||
* @return 如果是大端字节序返回 true
|
||||
*/
|
||||
bool PlatformDetector::isBigEndian() {
|
||||
return !isLittleEndian();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取平台信息摘要
|
||||
* @return 平台信息字符串
|
||||
*/
|
||||
std::string PlatformDetector::getPlatformSummary() {
|
||||
std::string summary;
|
||||
summary += "Platform: ";
|
||||
summary += platformName();
|
||||
summary += "\n";
|
||||
summary += "Memory: ";
|
||||
summary += std::to_string(getSystemMemoryMB());
|
||||
summary += " MB\n";
|
||||
summary += "CPU Cores: ";
|
||||
summary += std::to_string(getCPUCoreCount());
|
||||
summary += "\n";
|
||||
summary += "Endianness: ";
|
||||
summary += isLittleEndian() ? "Little Endian" : "Big Endian";
|
||||
summary += "\n";
|
||||
summary += "Desktop Platform: ";
|
||||
summary += isDesktopPlatform() ? "Yes" : "No";
|
||||
summary += "\n";
|
||||
summary += "Console Platform: ";
|
||||
summary += isConsolePlatform() ? "Yes" : "No";
|
||||
summary += "\n";
|
||||
summary += "Recommended Resolution: ";
|
||||
int width, height;
|
||||
getRecommendedResolution(width, height);
|
||||
summary += std::to_string(width);
|
||||
summary += "x";
|
||||
summary += std::to_string(height);
|
||||
summary += "\n";
|
||||
summary += "Default DPI: ";
|
||||
summary += std::to_string(static_cast<int>(getDefaultDPI()));
|
||||
return summary;
|
||||
}
|
||||
|
||||
PlatformCapabilities PlatformDetector::getWindowsCapabilities() {
|
||||
PlatformCapabilities caps;
|
||||
caps.supportsWindowed = true;
|
||||
caps.supportsFullscreen = true;
|
||||
caps.supportsBorderless = true;
|
||||
caps.supportsCursor = true;
|
||||
caps.supportsCursorHide = true;
|
||||
caps.supportsDPIAwareness = true;
|
||||
caps.supportsVSync = true;
|
||||
caps.supportsMultiMonitor = true;
|
||||
caps.supportsClipboard = true;
|
||||
caps.supportsGamepad = true;
|
||||
caps.supportsTouch = false;
|
||||
caps.supportsKeyboard = true;
|
||||
caps.supportsMouse = true;
|
||||
caps.supportsResize = true;
|
||||
caps.supportsHighDPI = true;
|
||||
caps.maxTextureSize = 16384;
|
||||
caps.preferredScreenWidth = 1920;
|
||||
caps.preferredScreenHeight = 1080;
|
||||
caps.defaultDPI = 96.0f;
|
||||
return caps;
|
||||
}
|
||||
|
||||
PlatformCapabilities PlatformDetector::getLinuxCapabilities() {
|
||||
PlatformCapabilities caps;
|
||||
caps.supportsWindowed = true;
|
||||
caps.supportsFullscreen = true;
|
||||
caps.supportsBorderless = true;
|
||||
caps.supportsCursor = true;
|
||||
caps.supportsCursorHide = true;
|
||||
caps.supportsDPIAwareness = true;
|
||||
caps.supportsVSync = true;
|
||||
caps.supportsMultiMonitor = true;
|
||||
caps.supportsClipboard = true;
|
||||
caps.supportsGamepad = true;
|
||||
caps.supportsTouch = false;
|
||||
caps.supportsKeyboard = true;
|
||||
caps.supportsMouse = true;
|
||||
caps.supportsResize = true;
|
||||
caps.supportsHighDPI = true;
|
||||
caps.maxTextureSize = 16384;
|
||||
caps.preferredScreenWidth = 1920;
|
||||
caps.preferredScreenHeight = 1080;
|
||||
caps.defaultDPI = 96.0f;
|
||||
return caps;
|
||||
}
|
||||
|
||||
PlatformCapabilities PlatformDetector::getMacOSCapabilities() {
|
||||
PlatformCapabilities caps;
|
||||
caps.supportsWindowed = true;
|
||||
caps.supportsFullscreen = true;
|
||||
caps.supportsBorderless = true;
|
||||
caps.supportsCursor = true;
|
||||
caps.supportsCursorHide = true;
|
||||
caps.supportsDPIAwareness = true;
|
||||
caps.supportsVSync = true;
|
||||
caps.supportsMultiMonitor = true;
|
||||
caps.supportsClipboard = true;
|
||||
caps.supportsGamepad = true;
|
||||
caps.supportsTouch = false;
|
||||
caps.supportsKeyboard = true;
|
||||
caps.supportsMouse = true;
|
||||
caps.supportsResize = true;
|
||||
caps.supportsHighDPI = true;
|
||||
caps.maxTextureSize = 16384;
|
||||
caps.preferredScreenWidth = 1920;
|
||||
caps.preferredScreenHeight = 1080;
|
||||
caps.defaultDPI = 144.0f;
|
||||
return caps;
|
||||
}
|
||||
|
||||
PlatformCapabilities PlatformDetector::getSwitchCapabilities() {
|
||||
PlatformCapabilities caps;
|
||||
caps.supportsWindowed = false;
|
||||
caps.supportsFullscreen = true;
|
||||
caps.supportsBorderless = false;
|
||||
caps.supportsCursor = false;
|
||||
caps.supportsCursorHide = false;
|
||||
caps.supportsDPIAwareness = false;
|
||||
caps.supportsVSync = true;
|
||||
caps.supportsMultiMonitor = false;
|
||||
caps.supportsClipboard = false;
|
||||
caps.supportsGamepad = true;
|
||||
caps.supportsTouch = true;
|
||||
caps.supportsKeyboard = false;
|
||||
caps.supportsMouse = false;
|
||||
caps.supportsResize = false;
|
||||
caps.supportsHighDPI = false;
|
||||
caps.maxTextureSize = 8192;
|
||||
caps.preferredScreenWidth = 1920;
|
||||
caps.preferredScreenHeight = 1080;
|
||||
caps.defaultDPI = 96.0f;
|
||||
return caps;
|
||||
}
|
||||
|
||||
AppConfig PlatformDetector::getWindowsDefaults() {
|
||||
AppConfig config = AppConfig::createDefault();
|
||||
return config;
|
||||
}
|
||||
|
||||
AppConfig PlatformDetector::getLinuxDefaults() {
|
||||
AppConfig config = AppConfig::createDefault();
|
||||
return config;
|
||||
}
|
||||
|
||||
AppConfig PlatformDetector::getMacOSDefaults() {
|
||||
AppConfig config = AppConfig::createDefault();
|
||||
return config;
|
||||
}
|
||||
|
||||
AppConfig PlatformDetector::getSwitchDefaults() {
|
||||
AppConfig config = AppConfig::createDefault();
|
||||
return config;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
#include <extra2d/core/module.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ModuleContext 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
ModuleContext::ModuleContext(std::vector<Module*>& modules)
|
||||
: modules_(modules)
|
||||
, index_(-1) {
|
||||
}
|
||||
|
||||
void ModuleContext::next() {
|
||||
index_++;
|
||||
if (index_ < static_cast<int>(modules_.size())) {
|
||||
handle(modules_[index_]);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// UpdateContext 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
UpdateContext::UpdateContext(std::vector<Module*>& modules, float deltaTime)
|
||||
: ModuleContext(modules)
|
||||
, deltaTime_(deltaTime) {
|
||||
}
|
||||
|
||||
void UpdateContext::handle(Module* m) {
|
||||
m->onUpdate(*this);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// RenderContext 实现
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
RenderContext::RenderContext(std::vector<Module*>& modules, Phase phase)
|
||||
: ModuleContext(modules)
|
||||
, phase_(phase) {
|
||||
}
|
||||
|
||||
void RenderContext::handle(Module* m) {
|
||||
switch (phase_) {
|
||||
case Phase::Before:
|
||||
m->beforeRender(*this);
|
||||
break;
|
||||
case Phase::On:
|
||||
m->onRender(*this);
|
||||
break;
|
||||
case Phase::After:
|
||||
m->afterRender(*this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,239 @@
|
|||
#include <extra2d/core/module.h>
|
||||
#include <extra2d/core/module_macros.h>
|
||||
#include <extra2d/core/module_meta.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
ModuleRegistry &ModuleRegistry::instance() {
|
||||
static ModuleRegistry instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void ModuleRegistry::registerMeta(ModuleMetaBase *meta) {
|
||||
if (!meta)
|
||||
return;
|
||||
|
||||
for (const auto *existing : metas_) {
|
||||
if (existing && std::string(existing->getName()) == meta->getName()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
metas_.push_back(meta);
|
||||
}
|
||||
|
||||
std::vector<ModuleMetaBase *> ModuleRegistry::getAllMetas() const {
|
||||
return metas_;
|
||||
}
|
||||
|
||||
ModuleMetaBase *ModuleRegistry::getMeta(const char *name) const {
|
||||
if (!name)
|
||||
return nullptr;
|
||||
|
||||
for (auto *meta : metas_) {
|
||||
if (meta && std::string(meta->getName()) == name) {
|
||||
return meta;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Module *ModuleRegistry::getModule(const char *name) const {
|
||||
auto it = instanceMap_.find(name ? name : "");
|
||||
return it != instanceMap_.end() ? it->second : nullptr;
|
||||
}
|
||||
|
||||
bool ModuleRegistry::hasModule(const char *name) const {
|
||||
return instanceMap_.find(name ? name : "") != instanceMap_.end();
|
||||
}
|
||||
|
||||
std::vector<Module *> ModuleRegistry::getAllModules() const {
|
||||
std::vector<Module *> result;
|
||||
result.reserve(instances_.size());
|
||||
for (const auto &inst : instances_) {
|
||||
result.push_back(inst.get());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ModuleRegistry::hasCircularDependency() const {
|
||||
std::unordered_map<std::string, std::unordered_set<std::string>> graph;
|
||||
|
||||
for (auto *meta : metas_) {
|
||||
if (!meta)
|
||||
continue;
|
||||
std::string name = meta->getName();
|
||||
for (const auto *dep : meta->getDependencies()) {
|
||||
graph[name].insert(dep);
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<std::string> visited;
|
||||
std::unordered_set<std::string> recStack;
|
||||
|
||||
std::function<bool(const std::string &)> hasCycle =
|
||||
[&](const std::string &node) -> bool {
|
||||
visited.insert(node);
|
||||
recStack.insert(node);
|
||||
|
||||
for (const auto &neighbor : graph[node]) {
|
||||
if (visited.find(neighbor) == visited.end()) {
|
||||
if (hasCycle(neighbor))
|
||||
return true;
|
||||
} else if (recStack.find(neighbor) != recStack.end()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
recStack.erase(node);
|
||||
return false;
|
||||
};
|
||||
|
||||
for (auto *meta : metas_) {
|
||||
if (!meta)
|
||||
continue;
|
||||
std::string name = meta->getName();
|
||||
if (visited.find(name) == visited.end()) {
|
||||
if (hasCycle(name))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<ModuleMetaBase *> ModuleRegistry::sortByDependency() {
|
||||
if (hasCircularDependency()) {
|
||||
E2D_LOG_ERROR("Circular dependency detected in modules!");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, int> inDegree;
|
||||
std::unordered_map<std::string, std::vector<std::string>> graph;
|
||||
std::unordered_map<std::string, ModuleMetaBase *> nameToMeta;
|
||||
|
||||
for (auto *meta : metas_) {
|
||||
if (!meta)
|
||||
continue;
|
||||
std::string name = meta->getName();
|
||||
nameToMeta[name] = meta;
|
||||
if (inDegree.find(name) == inDegree.end()) {
|
||||
inDegree[name] = 0;
|
||||
}
|
||||
|
||||
for (const auto *dep : meta->getDependencies()) {
|
||||
std::string depName = dep;
|
||||
graph[depName].push_back(name);
|
||||
inDegree[name]++;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ModuleMetaBase *> sorted;
|
||||
std::vector<std::string> currentLevel;
|
||||
|
||||
for (const auto &[name, degree] : inDegree) {
|
||||
if (degree == 0) {
|
||||
currentLevel.push_back(name);
|
||||
}
|
||||
}
|
||||
|
||||
while (!currentLevel.empty()) {
|
||||
std::sort(currentLevel.begin(), currentLevel.end(),
|
||||
[&nameToMeta](const std::string &a, const std::string &b) {
|
||||
auto *metaA = nameToMeta[a];
|
||||
auto *metaB = nameToMeta[b];
|
||||
if (!metaA || !metaB)
|
||||
return a < b;
|
||||
return metaA->getPriority() < metaB->getPriority();
|
||||
});
|
||||
|
||||
std::vector<std::string> nextLevel;
|
||||
|
||||
for (const auto &name : currentLevel) {
|
||||
if (nameToMeta.find(name) != nameToMeta.end()) {
|
||||
sorted.push_back(nameToMeta[name]);
|
||||
}
|
||||
|
||||
for (const auto &neighbor : graph[name]) {
|
||||
inDegree[neighbor]--;
|
||||
if (inDegree[neighbor] == 0) {
|
||||
nextLevel.push_back(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentLevel = std::move(nextLevel);
|
||||
}
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
bool ModuleRegistry::createAndInitAll() {
|
||||
if (initialized_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 动态库中模块已通过 E2D_MODULE 宏自动注册
|
||||
// 无需手动调用 registerCoreModules()
|
||||
|
||||
E2D_LOG_INFO("ModuleRegistry: {} modules registered", metas_.size());
|
||||
for (auto *meta : metas_) {
|
||||
if (meta) {
|
||||
E2D_LOG_INFO(" - {} (priority: {})", meta->getName(),
|
||||
meta->getPriority());
|
||||
}
|
||||
}
|
||||
|
||||
auto sorted = sortByDependency();
|
||||
if (sorted.empty() && !metas_.empty()) {
|
||||
E2D_LOG_ERROR(
|
||||
"Failed to sort modules by dependency - possible circular dependency");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto *meta : sorted) {
|
||||
if (!meta)
|
||||
continue;
|
||||
|
||||
Module *instance = meta->create();
|
||||
if (!instance) {
|
||||
E2D_LOG_ERROR("Failed to create module: {}", meta->getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
instance->setupModule();
|
||||
|
||||
instances_.emplace_back(instance);
|
||||
instanceMap_[meta->getName()] = instance;
|
||||
}
|
||||
|
||||
initialized_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ModuleRegistry::destroyAll() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Destroying {} modules in reverse order", instances_.size());
|
||||
|
||||
// 先销毁所有模块(逆序)
|
||||
for (auto it = instances_.rbegin(); it != instances_.rend(); ++it) {
|
||||
if (*it) {
|
||||
E2D_LOG_INFO("Destroying module: {}", (*it)->getName());
|
||||
(*it)->destroyModule();
|
||||
}
|
||||
}
|
||||
|
||||
// 然后清理实例
|
||||
instances_.clear();
|
||||
instanceMap_.clear();
|
||||
initialized_ = false;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
#include <extra2d/core/service_registry.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
ServiceRegistry& ServiceRegistry::instance() {
|
||||
static ServiceRegistry instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void ServiceRegistry::setServiceEnabled(const std::string& name, bool enabled) {
|
||||
for (auto& reg : registrations_) {
|
||||
if (reg.name == name) {
|
||||
reg.enabled = enabled;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ServiceRegistry::createAllServices() {
|
||||
std::sort(registrations_.begin(), registrations_.end(),
|
||||
[](const ServiceRegistration& a, const ServiceRegistration& b) {
|
||||
return static_cast<int>(a.priority) < static_cast<int>(b.priority);
|
||||
});
|
||||
|
||||
for (const auto& reg : registrations_) {
|
||||
if (!reg.enabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto service = reg.factory();
|
||||
if (service) {
|
||||
ServiceLocator::instance().registerService<IService>(service);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -2,15 +2,6 @@
|
|||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 创建窗口大小改变事件
|
||||
*
|
||||
* 创建一个表示窗口尺寸变化的Event对象
|
||||
*
|
||||
* @param width 新的窗口宽度(像素)
|
||||
* @param height 新的窗口高度(像素)
|
||||
* @return 包含窗口大小改变信息的Event对象
|
||||
*/
|
||||
Event Event::createWindowResize(int width, int height) {
|
||||
Event event;
|
||||
event.type = EventType::WindowResize;
|
||||
|
|
@ -18,115 +9,50 @@ Event Event::createWindowResize(int width, int height) {
|
|||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建窗口关闭事件
|
||||
*
|
||||
* 创建一个表示窗口请求关闭的Event对象
|
||||
*
|
||||
* @return 表示窗口关闭请求的Event对象
|
||||
*/
|
||||
Event Event::createWindowClose() {
|
||||
Event event;
|
||||
event.type = EventType::WindowClose;
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建键盘按键按下事件
|
||||
*
|
||||
* 创建一个表示键盘按键被按下的Event对象
|
||||
*
|
||||
* @param keyCode 按键码
|
||||
* @param scancode 扫描码
|
||||
* @param mods 修饰键状态(如Shift、Ctrl等)
|
||||
* @return 包含按键按下信息的Event对象
|
||||
*/
|
||||
Event Event::createKeyPress(int keyCode, int scancode, int mods) {
|
||||
Event event;
|
||||
event.type = EventType::KeyPressed;
|
||||
event.type = EventType::KeyPress;
|
||||
event.data = KeyEvent{keyCode, scancode, mods};
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建键盘按键释放事件
|
||||
*
|
||||
* 创建一个表示键盘按键被释放的Event对象
|
||||
*
|
||||
* @param keyCode 按键码
|
||||
* @param scancode 扫描码
|
||||
* @param mods 修饰键状态(如Shift、Ctrl等)
|
||||
* @return 包含按键释放信息的Event对象
|
||||
*/
|
||||
Event Event::createKeyRelease(int keyCode, int scancode, int mods) {
|
||||
Event event;
|
||||
event.type = EventType::KeyReleased;
|
||||
event.type = EventType::KeyRelease;
|
||||
event.data = KeyEvent{keyCode, scancode, mods};
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建鼠标按钮按下事件
|
||||
*
|
||||
* 创建一个表示鼠标按钮被按下的Event对象
|
||||
*
|
||||
* @param button 鼠标按钮编号
|
||||
* @param mods 修饰键状态
|
||||
* @param pos 鼠标按下时的位置坐标
|
||||
* @return 包含鼠标按钮按下信息的Event对象
|
||||
*/
|
||||
Event Event::createMouseButtonPress(int button, int mods, const Vec2 &pos) {
|
||||
Event Event::createMousePress(int button, int mods, const Vec2 &pos) {
|
||||
Event event;
|
||||
event.type = EventType::MouseButtonPressed;
|
||||
event.data = MouseButtonEvent{button, mods, pos};
|
||||
event.type = EventType::MousePress;
|
||||
event.data = MouseEvent{button, mods, pos};
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建鼠标按钮释放事件
|
||||
*
|
||||
* 创建一个表示鼠标按钮被释放的Event对象
|
||||
*
|
||||
* @param button 鼠标按钮编号
|
||||
* @param mods 修饰键状态
|
||||
* @param pos 鼠标释放时的位置坐标
|
||||
* @return 包含鼠标按钮释放信息的Event对象
|
||||
*/
|
||||
Event Event::createMouseButtonRelease(int button, int mods, const Vec2 &pos) {
|
||||
Event Event::createMouseRelease(int button, int mods, const Vec2 &pos) {
|
||||
Event event;
|
||||
event.type = EventType::MouseButtonReleased;
|
||||
event.data = MouseButtonEvent{button, mods, pos};
|
||||
event.type = EventType::MouseRelease;
|
||||
event.data = MouseEvent{button, mods, pos};
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建鼠标移动事件
|
||||
*
|
||||
* 创建一个表示鼠标移动的Event对象
|
||||
*
|
||||
* @param pos 鼠标当前位置坐标
|
||||
* @param delta 鼠标移动的位移量
|
||||
* @return 包含鼠标移动信息的Event对象
|
||||
*/
|
||||
Event Event::createMouseMove(const Vec2 &pos, const Vec2 &delta) {
|
||||
Event event;
|
||||
event.type = EventType::MouseMoved;
|
||||
event.type = EventType::MouseMove;
|
||||
event.data = MouseMoveEvent{pos, delta};
|
||||
return event;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建鼠标滚轮滚动事件
|
||||
*
|
||||
* 创建一个表示鼠标滚轮滚动的Event对象
|
||||
*
|
||||
* @param offset 滚轮滚动的偏移量
|
||||
* @param pos 滚动时鼠标的位置坐标
|
||||
* @return 包含鼠标滚轮滚动信息的Event对象
|
||||
*/
|
||||
Event Event::createMouseScroll(const Vec2 &offset, const Vec2 &pos) {
|
||||
Event event;
|
||||
event.type = EventType::MouseScrolled;
|
||||
event.type = EventType::MouseScroll;
|
||||
event.data = MouseScrollEvent{offset, pos};
|
||||
return event;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +1,42 @@
|
|||
#include <extra2d/graphics/shader/shader_manager.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
* @return Shader管理器实例引用
|
||||
*/
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// ============================================================================
|
||||
// ShaderManager 核心
|
||||
// ============================================================================
|
||||
|
||||
ShaderManager& ShaderManager::getInstance() {
|
||||
static ShaderManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 使用平台默认路径初始化Shader系统
|
||||
* 自动检测平台并使用正确的路径(romfs/sdmc/相对路径)
|
||||
* @param factory 渲染后端Shader工厂
|
||||
* @param appName 应用名称(用于缓存目录)
|
||||
* @return 初始化成功返回true,失败返回false
|
||||
*/
|
||||
bool ShaderManager::init(Ptr<IShaderFactory> factory, const std::string& appName) {
|
||||
std::string shaderDir = PlatformDetector::getShaderPath(appName);
|
||||
std::string cacheDir = PlatformDetector::getShaderCachePath(appName);
|
||||
|
||||
hotReloadSupported_ = PlatformDetector::supportsHotReload();
|
||||
|
||||
E2D_LOG_INFO("Platform: {} (HotReload: {})",
|
||||
PlatformDetector::platformName(),
|
||||
hotReloadSupported_ ? "supported" : "not supported");
|
||||
|
||||
std::string shaderDir;
|
||||
std::string cacheDir;
|
||||
|
||||
#ifdef __SWITCH__
|
||||
shaderDir = "romfs:/shaders/";
|
||||
cacheDir = "sdmc:/config/" + appName + "/shader_cache/";
|
||||
hotReloadSupported_ = false;
|
||||
E2D_LOG_INFO("Platform: Switch (HotReload: not supported)");
|
||||
#else
|
||||
shaderDir = "shaders/";
|
||||
cacheDir = "shader_cache/";
|
||||
hotReloadSupported_ = true;
|
||||
E2D_LOG_INFO("Platform: Desktop (HotReload: supported)");
|
||||
#endif
|
||||
|
||||
return init(shaderDir, cacheDir, factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化Shader系统
|
||||
* @param shaderDir Shader文件目录
|
||||
* @param cacheDir 缓存目录
|
||||
* @param factory 渲染后端Shader工厂
|
||||
* @return 初始化成功返回true,失败返回false
|
||||
*/
|
||||
bool ShaderManager::init(const std::string& shaderDir,
|
||||
const std::string& cacheDir,
|
||||
Ptr<IShaderFactory> factory) {
|
||||
|
|
@ -56,20 +54,18 @@ bool ShaderManager::init(const std::string& shaderDir,
|
|||
cacheDir_ = cacheDir;
|
||||
factory_ = factory;
|
||||
|
||||
hotReloadSupported_ = PlatformDetector::supportsHotReload();
|
||||
|
||||
#ifdef __SWITCH__
|
||||
if (!ShaderCache::getInstance().init(cacheDir_)) {
|
||||
E2D_LOG_WARN("Failed to initialize shader cache on Switch");
|
||||
}
|
||||
hotReloadSupported_ = false;
|
||||
#else
|
||||
if (!ShaderCache::getInstance().init(cacheDir_)) {
|
||||
E2D_LOG_WARN("Failed to initialize shader cache, caching disabled");
|
||||
}
|
||||
hotReloadSupported_ = true;
|
||||
#endif
|
||||
|
||||
if (!initCache(cacheDir_)) {
|
||||
E2D_LOG_WARN("Failed to initialize shader cache, caching disabled");
|
||||
}
|
||||
|
||||
if (hotReloadSupported_) {
|
||||
if (!ShaderHotReloader::getInstance().init()) {
|
||||
if (!initReloader()) {
|
||||
E2D_LOG_WARN("Failed to initialize hot reloader");
|
||||
}
|
||||
}
|
||||
|
|
@ -78,25 +74,19 @@ bool ShaderManager::init(const std::string& shaderDir,
|
|||
|
||||
initialized_ = true;
|
||||
E2D_LOG_INFO("ShaderManager initialized");
|
||||
E2D_LOG_INFO(" Shader directory: {}", shaderDir_);
|
||||
E2D_LOG_INFO(" Cache directory: {}", cacheDir_);
|
||||
E2D_LOG_INFO(" Hot reload: {}", hotReloadSupported_ ? "supported" : "not supported");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 关闭Shader系统
|
||||
*/
|
||||
void ShaderManager::shutdown() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hotReloadSupported_) {
|
||||
ShaderHotReloader::getInstance().shutdown();
|
||||
shutdownReloader();
|
||||
}
|
||||
ShaderCache::getInstance().shutdown();
|
||||
shutdownCache();
|
||||
|
||||
shaders_.clear();
|
||||
factory_.reset();
|
||||
|
|
@ -105,175 +95,61 @@ void ShaderManager::shutdown() {
|
|||
E2D_LOG_INFO("ShaderManager shutdown");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从分离文件加载Shader
|
||||
* @param name Shader名称
|
||||
* @param vertPath 顶点着色器文件路径
|
||||
* @param fragPath 片段着色器文件路径
|
||||
* @return 加载的Shader实例
|
||||
*/
|
||||
// ============================================================================
|
||||
// Shader 加载
|
||||
// ============================================================================
|
||||
|
||||
Ptr<IShader> ShaderManager::loadFromFiles(const std::string& name,
|
||||
const std::string& vertPath,
|
||||
const std::string& fragPath) {
|
||||
if (!initialized_) {
|
||||
E2D_LOG_ERROR("ShaderManager not initialized");
|
||||
if (!factory_) {
|
||||
E2D_LOG_ERROR("Shader factory not initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto it = shaders_.find(name);
|
||||
if (it != shaders_.end()) {
|
||||
return it->second.shader;
|
||||
}
|
||||
auto vertSource = loader_.readFile(vertPath);
|
||||
auto fragSource = loader_.readFile(fragPath);
|
||||
|
||||
ShaderLoadResult result = loader_.loadFromSeparateFiles(name, vertPath, fragPath);
|
||||
if (!result.success) {
|
||||
E2D_LOG_ERROR("Failed to load shader files: {} - {}", vertPath, fragPath);
|
||||
if (vertSource.empty() || fragSource.empty()) {
|
||||
E2D_LOG_ERROR("Failed to load shader sources: {} / {}", vertPath, fragPath);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string sourceHash = ShaderCache::computeHash(result.vertSource, result.fragSource);
|
||||
Ptr<IShader> shader = loadFromCache(name, sourceHash, result.vertSource, result.fragSource);
|
||||
|
||||
if (!shader) {
|
||||
E2D_LOG_DEBUG("No valid cache found, compiling shader from source: {}", name);
|
||||
shader = factory_->createFromSource(name, result.vertSource, result.fragSource);
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to create shader from source: {}", name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> binary;
|
||||
if (factory_->getShaderBinary(*shader, binary)) {
|
||||
E2D_LOG_DEBUG("Got shader binary, size: {} bytes", binary.size());
|
||||
ShaderCacheEntry entry;
|
||||
entry.name = name;
|
||||
entry.sourceHash = sourceHash;
|
||||
entry.binary = binary;
|
||||
entry.dependencies = result.dependencies;
|
||||
ShaderCache::getInstance().saveCache(entry);
|
||||
} else {
|
||||
E2D_LOG_WARN("Failed to get shader binary for: {}", name);
|
||||
}
|
||||
}
|
||||
|
||||
ShaderInfo info;
|
||||
info.shader = shader;
|
||||
info.vertSource = result.vertSource;
|
||||
info.fragSource = result.fragSource;
|
||||
info.filePaths = {vertPath, fragPath};
|
||||
info.filePaths.insert(info.filePaths.end(), result.dependencies.begin(), result.dependencies.end());
|
||||
|
||||
info.metadata.name = name;
|
||||
info.metadata.vertPath = vertPath;
|
||||
info.metadata.fragPath = fragPath;
|
||||
|
||||
shaders_[name] = std::move(info);
|
||||
|
||||
if (hotReloadEnabled_ && hotReloadSupported_) {
|
||||
auto callback = [this, name](const FileChangeEvent& event) {
|
||||
this->handleFileChange(name, event);
|
||||
};
|
||||
ShaderHotReloader::getInstance().watch(name, shaders_[name].filePaths, callback);
|
||||
}
|
||||
|
||||
E2D_LOG_DEBUG("Shader loaded: {}", name);
|
||||
return shader;
|
||||
return loadFromSource(name, vertSource, fragSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从组合文件加载Shader
|
||||
* @param path 组合Shader文件路径
|
||||
* @return 加载的Shader实例
|
||||
*/
|
||||
Ptr<IShader> ShaderManager::loadFromCombinedFile(const std::string& path) {
|
||||
if (!initialized_) {
|
||||
E2D_LOG_ERROR("ShaderManager not initialized");
|
||||
if (!factory_) {
|
||||
E2D_LOG_ERROR("Shader factory not initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ShaderMetadata metadata = loader_.getMetadata(path);
|
||||
std::string name = metadata.name.empty() ? path : metadata.name;
|
||||
|
||||
auto it = shaders_.find(name);
|
||||
if (it != shaders_.end()) {
|
||||
return it->second.shader;
|
||||
}
|
||||
|
||||
ShaderLoadResult result = loader_.loadFromCombinedFile(path);
|
||||
auto result = loader_.loadFromCombinedFile(path);
|
||||
if (!result.success) {
|
||||
E2D_LOG_ERROR("Failed to load combined shader file: {}", path);
|
||||
E2D_LOG_ERROR("Failed to load combined shader: {}", path);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string sourceHash = ShaderCache::computeHash(result.vertSource, result.fragSource);
|
||||
Ptr<IShader> shader = loadFromCache(name, sourceHash, result.vertSource, result.fragSource);
|
||||
|
||||
if (!shader) {
|
||||
E2D_LOG_DEBUG("No valid cache found, compiling shader from source: {}", name);
|
||||
shader = factory_->createFromSource(name, result.vertSource, result.fragSource);
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to create shader from source: {}", name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> binary;
|
||||
if (factory_->getShaderBinary(*shader, binary)) {
|
||||
E2D_LOG_DEBUG("Got shader binary, size: {} bytes", binary.size());
|
||||
ShaderCacheEntry entry;
|
||||
entry.name = name;
|
||||
entry.sourceHash = sourceHash;
|
||||
entry.binary = binary;
|
||||
entry.dependencies = result.dependencies;
|
||||
ShaderCache::getInstance().saveCache(entry);
|
||||
} else {
|
||||
E2D_LOG_WARN("Failed to get shader binary for: {}", name);
|
||||
}
|
||||
}
|
||||
|
||||
ShaderInfo info;
|
||||
info.shader = shader;
|
||||
info.vertSource = result.vertSource;
|
||||
info.fragSource = result.fragSource;
|
||||
info.filePaths = {path};
|
||||
info.filePaths.insert(info.filePaths.end(), result.dependencies.begin(), result.dependencies.end());
|
||||
info.metadata = metadata;
|
||||
|
||||
shaders_[name] = std::move(info);
|
||||
|
||||
if (hotReloadEnabled_ && hotReloadSupported_) {
|
||||
auto callback = [this, name](const FileChangeEvent& event) {
|
||||
this->handleFileChange(name, event);
|
||||
};
|
||||
ShaderHotReloader::getInstance().watch(name, shaders_[name].filePaths, callback);
|
||||
}
|
||||
|
||||
E2D_LOG_DEBUG("Shader loaded from combined file: {}", name);
|
||||
return shader;
|
||||
std::string name = path.substr(path.find_last_of("/\\") + 1);
|
||||
return loadFromSource(name, result.vertSource, result.fragSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从源码加载Shader
|
||||
* @param name Shader名称
|
||||
* @param vertSource 顶点着色器源码
|
||||
* @param fragSource 片段着色器源码
|
||||
* @return 加载的Shader实例
|
||||
*/
|
||||
Ptr<IShader> ShaderManager::loadFromSource(const std::string& name,
|
||||
const std::string& vertSource,
|
||||
const std::string& fragSource) {
|
||||
if (!initialized_) {
|
||||
E2D_LOG_ERROR("ShaderManager not initialized");
|
||||
if (!factory_) {
|
||||
E2D_LOG_ERROR("Shader factory not initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto it = shaders_.find(name);
|
||||
if (it != shaders_.end()) {
|
||||
return it->second.shader;
|
||||
if (has(name)) {
|
||||
E2D_LOG_WARN("Shader already exists: {}", name);
|
||||
return get(name);
|
||||
}
|
||||
|
||||
Ptr<IShader> shader = factory_->createFromSource(name, vertSource, fragSource);
|
||||
auto shader = factory_->createFromSource(name, vertSource, fragSource);
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to create shader from source: {}", name);
|
||||
E2D_LOG_ERROR("Failed to create shader: {}", name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
|
@ -281,19 +157,13 @@ Ptr<IShader> ShaderManager::loadFromSource(const std::string& name,
|
|||
info.shader = shader;
|
||||
info.vertSource = vertSource;
|
||||
info.fragSource = fragSource;
|
||||
info.metadata.name = name;
|
||||
|
||||
shaders_[name] = std::move(info);
|
||||
|
||||
E2D_LOG_DEBUG("Shader loaded from source: {}", name);
|
||||
E2D_LOG_INFO("Shader loaded: {}", name);
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取已加载的Shader
|
||||
* @param name Shader名称
|
||||
* @return Shader实例,不存在返回nullptr
|
||||
*/
|
||||
Ptr<IShader> ShaderManager::get(const std::string& name) const {
|
||||
auto it = shaders_.find(name);
|
||||
if (it != shaders_.end()) {
|
||||
|
|
@ -302,232 +172,445 @@ Ptr<IShader> ShaderManager::get(const std::string& name) const {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查Shader是否存在
|
||||
* @param name Shader名称
|
||||
* @return 存在返回true,否则返回false
|
||||
*/
|
||||
bool ShaderManager::has(const std::string& name) const {
|
||||
return shaders_.find(name) != shaders_.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 移除Shader
|
||||
* @param name Shader名称
|
||||
*/
|
||||
void ShaderManager::remove(const std::string& name) {
|
||||
auto it = shaders_.find(name);
|
||||
if (it != shaders_.end()) {
|
||||
ShaderHotReloader::getInstance().unwatch(name);
|
||||
shaders_.erase(it);
|
||||
E2D_LOG_DEBUG("Shader removed: {}", name);
|
||||
E2D_LOG_INFO("Shader removed: {}", name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清除所有Shader
|
||||
*/
|
||||
void ShaderManager::clear() {
|
||||
if (hotReloadSupported_) {
|
||||
for (const auto& pair : shaders_) {
|
||||
ShaderHotReloader::getInstance().unwatch(pair.first);
|
||||
}
|
||||
}
|
||||
shaders_.clear();
|
||||
E2D_LOG_DEBUG("All shaders cleared");
|
||||
E2D_LOG_INFO("All shaders cleared");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册重载回调
|
||||
* @param name Shader名称
|
||||
* @param callback 重载回调函数
|
||||
*/
|
||||
// ============================================================================
|
||||
// 热重载
|
||||
// ============================================================================
|
||||
|
||||
void ShaderManager::setReloadCallback(const std::string& name, ShaderReloadCallback callback) {
|
||||
auto it = shaders_.find(name);
|
||||
if (it != shaders_.end()) {
|
||||
it->second.reloadCallback = callback;
|
||||
it->second.reloadCallback = std::move(callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 启用/禁用热重载
|
||||
* @param enabled 是否启用
|
||||
*/
|
||||
void ShaderManager::setHotReloadEnabled(bool enabled) {
|
||||
if (!hotReloadSupported_) {
|
||||
if (!hotReloadSupported_ && enabled) {
|
||||
E2D_LOG_WARN("Hot reload not supported on this platform");
|
||||
return;
|
||||
}
|
||||
hotReloadEnabled_ = enabled;
|
||||
ShaderHotReloader::getInstance().setEnabled(enabled);
|
||||
E2D_LOG_INFO("Hot reload {}", enabled ? "enabled" : "disabled");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查热重载是否启用
|
||||
* @return 启用返回true,否则返回false
|
||||
*/
|
||||
bool ShaderManager::isHotReloadEnabled() const {
|
||||
return hotReloadEnabled_ && hotReloadSupported_;
|
||||
return hotReloadEnabled_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新热重载系统(主循环调用)
|
||||
*/
|
||||
void ShaderManager::update() {
|
||||
if (hotReloadEnabled_ && hotReloadSupported_) {
|
||||
ShaderHotReloader::getInstance().update();
|
||||
if (!hotReloadEnabled_ || !hotReloadSupported_) {
|
||||
return;
|
||||
}
|
||||
if (reloaderInitialized_) {
|
||||
pollChanges();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 手动重载Shader
|
||||
* @param name Shader名称
|
||||
* @return 重载成功返回true,失败返回false
|
||||
*/
|
||||
bool ShaderManager::reload(const std::string& name) {
|
||||
auto it = shaders_.find(name);
|
||||
if (it == shaders_.end()) {
|
||||
E2D_LOG_WARN("Shader not found for reload: {}", name);
|
||||
E2D_LOG_ERROR("Shader not found: {}", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
ShaderInfo& info = it->second;
|
||||
|
||||
std::string vertSource = info.vertSource;
|
||||
std::string fragSource = info.fragSource;
|
||||
|
||||
if (!info.metadata.vertPath.empty() && !info.metadata.fragPath.empty()) {
|
||||
ShaderLoadResult result = loader_.loadFromSeparateFiles(name, info.metadata.vertPath, info.metadata.fragPath);
|
||||
if (result.success) {
|
||||
vertSource = result.vertSource;
|
||||
fragSource = result.fragSource;
|
||||
}
|
||||
} else if (!info.metadata.combinedPath.empty()) {
|
||||
ShaderLoadResult result = loader_.loadFromCombinedFile(info.metadata.combinedPath);
|
||||
if (result.success) {
|
||||
vertSource = result.vertSource;
|
||||
fragSource = result.fragSource;
|
||||
}
|
||||
}
|
||||
|
||||
Ptr<IShader> newShader = factory_->createFromSource(name, vertSource, fragSource);
|
||||
if (!newShader) {
|
||||
auto shader = factory_->createFromSource(name, it->second.vertSource, it->second.fragSource);
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to reload shader: {}", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
info.shader = newShader;
|
||||
info.vertSource = vertSource;
|
||||
info.fragSource = fragSource;
|
||||
it->second.shader = shader;
|
||||
|
||||
if (info.reloadCallback) {
|
||||
info.reloadCallback(newShader);
|
||||
if (it->second.reloadCallback) {
|
||||
it->second.reloadCallback(shader);
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Shader reloaded: {}", name);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取内置Shader
|
||||
* @param name 内置Shader名称
|
||||
* @return Shader实例
|
||||
*/
|
||||
Ptr<IShader> ShaderManager::getBuiltin(const std::string& name) {
|
||||
Ptr<IShader> shader = get(name);
|
||||
if (shader) {
|
||||
return shader;
|
||||
}
|
||||
// ============================================================================
|
||||
// 内置Shader
|
||||
// ============================================================================
|
||||
|
||||
std::string path = shaderDir_ + "builtin/" + name + ".shader";
|
||||
return loadFromCombinedFile(path);
|
||||
Ptr<IShader> ShaderManager::getBuiltin(const std::string& name) {
|
||||
return get("builtin_" + name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 加载所有内置Shader
|
||||
* @return 加载成功返回true,失败返回false
|
||||
*/
|
||||
bool ShaderManager::loadBuiltinShaders() {
|
||||
if (!initialized_) {
|
||||
E2D_LOG_ERROR("ShaderManager not initialized");
|
||||
return false;
|
||||
}
|
||||
E2D_LOG_INFO("Loading builtin shaders...");
|
||||
|
||||
bool allSuccess = true;
|
||||
|
||||
const char* builtinNames[] = {
|
||||
const std::vector<std::string> builtinShaders = {
|
||||
"sprite",
|
||||
"particle",
|
||||
"shape",
|
||||
"postprocess",
|
||||
"font"
|
||||
"font",
|
||||
"particle",
|
||||
"postprocess"
|
||||
};
|
||||
|
||||
for (const char* name : builtinNames) {
|
||||
int loaded = 0;
|
||||
for (const auto& name : builtinShaders) {
|
||||
std::string path = shaderDir_ + "builtin/" + name + ".shader";
|
||||
std::string shaderName = std::string("builtin_") + name;
|
||||
|
||||
if (!loadFromCombinedFile(path)) {
|
||||
E2D_LOG_ERROR("Failed to load builtin {} shader from: {}", name, path);
|
||||
allSuccess = false;
|
||||
} else {
|
||||
auto it = shaders_.find(name);
|
||||
if (it != shaders_.end()) {
|
||||
shaders_[shaderName] = it->second;
|
||||
shaders_.erase(it);
|
||||
if (ShaderLoader::fileExists(path)) {
|
||||
auto result = loader_.loadFromCombinedFile(path);
|
||||
if (result.success) {
|
||||
auto shader = factory_->createFromSource("builtin_" + name, result.vertSource, result.fragSource);
|
||||
if (shader) {
|
||||
ShaderInfo info;
|
||||
info.shader = shader;
|
||||
info.vertSource = result.vertSource;
|
||||
info.fragSource = result.fragSource;
|
||||
shaders_["builtin_" + name] = std::move(info);
|
||||
loaded++;
|
||||
E2D_LOG_DEBUG("Loaded builtin shader: {}", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allSuccess) {
|
||||
E2D_LOG_INFO("All builtin shaders loaded");
|
||||
}
|
||||
|
||||
return allSuccess;
|
||||
E2D_LOG_INFO("Builtin shaders loaded: {}/{}", loaded, builtinShaders.size());
|
||||
return loaded > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从缓存加载Shader
|
||||
* @param name Shader名称
|
||||
* @param sourceHash 源码哈希值
|
||||
* @param vertSource 顶点着色器源码
|
||||
* @param fragSource 片段着色器源码
|
||||
* @return Shader实例
|
||||
*/
|
||||
Ptr<IShader> ShaderManager::loadFromCache(const std::string& name,
|
||||
const std::string& sourceHash,
|
||||
const std::string& vertSource,
|
||||
const std::string& fragSource) {
|
||||
if (!ShaderCache::getInstance().isInitialized()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!ShaderCache::getInstance().hasValidCache(name, sourceHash)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Ptr<ShaderCacheEntry> entry = ShaderCache::getInstance().loadCache(name);
|
||||
if (!entry || entry->binary.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Ptr<IShader> shader = factory_->createFromBinary(name, entry->binary);
|
||||
if (shader) {
|
||||
E2D_LOG_DEBUG("Shader loaded from cache: {}", name);
|
||||
}
|
||||
|
||||
return shader;
|
||||
void ShaderManager::createBuiltinShaderSources() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 处理文件变化事件
|
||||
* @param shaderName Shader名称
|
||||
* @param event 文件变化事件
|
||||
*/
|
||||
void ShaderManager::handleFileChange(const std::string& shaderName, const FileChangeEvent& event) {
|
||||
E2D_LOG_DEBUG("Shader file changed: {} -> {}", shaderName, event.filepath);
|
||||
reload(shaderName);
|
||||
if (hotReloadEnabled_) {
|
||||
reload(shaderName);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 缓存(原 ShaderCache)
|
||||
// ============================================================================
|
||||
|
||||
bool ShaderManager::initCache(const std::string& cacheDir) {
|
||||
cacheDir_ = cacheDir;
|
||||
|
||||
if (!ensureCacheDirectory()) {
|
||||
E2D_LOG_ERROR("Failed to create cache directory: {}", cacheDir);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!loadCacheIndex()) {
|
||||
E2D_LOG_WARN("Failed to load cache index, starting fresh");
|
||||
}
|
||||
|
||||
cacheInitialized_ = true;
|
||||
E2D_LOG_INFO("Shader cache initialized at: {}", cacheDir);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShaderManager::shutdownCache() {
|
||||
if (!cacheInitialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
saveCacheIndex();
|
||||
cacheMap_.clear();
|
||||
cacheInitialized_ = false;
|
||||
E2D_LOG_INFO("Shader cache shutdown");
|
||||
}
|
||||
|
||||
bool ShaderManager::hasValidCache(const std::string& name, const std::string& sourceHash) {
|
||||
auto it = cacheMap_.find(name);
|
||||
if (it == cacheMap_.end()) {
|
||||
return false;
|
||||
}
|
||||
return it->second.sourceHash == sourceHash;
|
||||
}
|
||||
|
||||
Ptr<ShaderCacheEntry> ShaderManager::loadCache(const std::string& name) {
|
||||
auto it = cacheMap_.find(name);
|
||||
if (it == cacheMap_.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string cachePath = getCachePath(name);
|
||||
std::ifstream file(cachePath, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
E2D_LOG_WARN("Failed to open cache file: {}", cachePath);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto entry = std::make_shared<ShaderCacheEntry>(it->second);
|
||||
entry->binary.clear();
|
||||
|
||||
file.seekg(0, std::ios::end);
|
||||
size_t fileSize = static_cast<size_t>(file.tellg());
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
entry->binary.resize(fileSize);
|
||||
file.read(reinterpret_cast<char*>(entry->binary.data()), fileSize);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
bool ShaderManager::saveCache(const ShaderCacheEntry& entry) {
|
||||
if (!cacheInitialized_) {
|
||||
E2D_LOG_WARN("Shader cache not initialized, cannot save cache");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry.binary.empty()) {
|
||||
E2D_LOG_WARN("Shader binary is empty, skipping cache save for: {}", entry.name);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string cachePath = getCachePath(entry.name);
|
||||
E2D_LOG_DEBUG("Saving shader cache to: {} ({} bytes)", cachePath, entry.binary.size());
|
||||
|
||||
std::ofstream file(cachePath, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
E2D_LOG_ERROR("Failed to create cache file: {}", cachePath);
|
||||
return false;
|
||||
}
|
||||
|
||||
file.write(reinterpret_cast<const char*>(entry.binary.data()), entry.binary.size());
|
||||
file.close();
|
||||
|
||||
cacheMap_[entry.name] = entry;
|
||||
saveCacheIndex();
|
||||
|
||||
E2D_LOG_INFO("Shader cache saved: {} ({} bytes)", entry.name, entry.binary.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShaderManager::invalidateCache(const std::string& name) {
|
||||
auto it = cacheMap_.find(name);
|
||||
if (it == cacheMap_.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string cachePath = getCachePath(name);
|
||||
fs::remove(cachePath);
|
||||
|
||||
cacheMap_.erase(it);
|
||||
saveCacheIndex();
|
||||
|
||||
E2D_LOG_DEBUG("Shader cache invalidated: {}", name);
|
||||
}
|
||||
|
||||
void ShaderManager::clearAllCache() {
|
||||
for (const auto& pair : cacheMap_) {
|
||||
std::string cachePath = getCachePath(pair.first);
|
||||
fs::remove(cachePath);
|
||||
}
|
||||
|
||||
cacheMap_.clear();
|
||||
saveCacheIndex();
|
||||
|
||||
E2D_LOG_INFO("All shader caches cleared");
|
||||
}
|
||||
|
||||
std::string ShaderManager::computeHash(const std::string& vertSource,
|
||||
const std::string& fragSource) {
|
||||
std::string combined = vertSource + fragSource;
|
||||
|
||||
uint32_t hash = 5381;
|
||||
for (char c : combined) {
|
||||
hash = ((hash << 5) + hash) + static_cast<uint32_t>(c);
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
ss << std::hex << hash;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
bool ShaderManager::loadCacheIndex() {
|
||||
std::string indexPath = cacheDir_ + "/.cache_index";
|
||||
|
||||
if (!fs::exists(indexPath)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::ifstream file(indexPath);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
if (line.empty() || line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t pos = line.find('=');
|
||||
if (pos == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string name = line.substr(0, pos);
|
||||
std::string hash = line.substr(pos + 1);
|
||||
|
||||
std::string cachePath = getCachePath(name);
|
||||
if (fs::exists(cachePath)) {
|
||||
ShaderCacheEntry entry;
|
||||
entry.name = name;
|
||||
entry.sourceHash = hash;
|
||||
entry.compileTime = static_cast<uint64_t>(
|
||||
std::chrono::system_clock::now().time_since_epoch().count());
|
||||
cacheMap_[name] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShaderManager::saveCacheIndex() {
|
||||
std::string indexPath = cacheDir_ + "/.cache_index";
|
||||
|
||||
std::ofstream file(indexPath);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file << "# Extra2D Shader Cache Index\n";
|
||||
file << "# Format: name=hash\n";
|
||||
|
||||
for (const auto& pair : cacheMap_) {
|
||||
file << pair.first << "=" << pair.second.sourceHash << "\n";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string ShaderManager::getCachePath(const std::string& name) const {
|
||||
return cacheDir_ + "/" + name + ".cache";
|
||||
}
|
||||
|
||||
bool ShaderManager::ensureCacheDirectory() {
|
||||
if (cacheDir_.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
if (!fs::exists(cacheDir_)) {
|
||||
if (!fs::create_directories(cacheDir_, ec)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 热重载(原 ShaderHotReloader)
|
||||
// ============================================================================
|
||||
|
||||
bool ShaderManager::initReloader() {
|
||||
if (reloaderInitialized_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
watchBuffer_.resize(4096);
|
||||
#endif
|
||||
|
||||
reloaderInitialized_ = true;
|
||||
E2D_LOG_INFO("Shader hot reloader initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
void ShaderManager::shutdownReloader() {
|
||||
if (!reloaderInitialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (watchHandle_ != nullptr) {
|
||||
FindCloseChangeNotification(watchHandle_);
|
||||
watchHandle_ = nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
watchMap_.clear();
|
||||
reloaderInitialized_ = false;
|
||||
E2D_LOG_INFO("Shader hot reloader shutdown");
|
||||
}
|
||||
|
||||
void ShaderManager::watch(const std::string& shaderName,
|
||||
const std::vector<std::string>& filePaths,
|
||||
FileChangeCallback callback) {
|
||||
if (!reloaderInitialized_) {
|
||||
E2D_LOG_WARN("Hot reloader not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
WatchInfo info;
|
||||
info.filePaths = filePaths;
|
||||
info.callback = callback;
|
||||
|
||||
for (const auto& path : filePaths) {
|
||||
info.modifiedTimes[path] = getFileModifiedTime(path);
|
||||
}
|
||||
|
||||
watchMap_[shaderName] = std::move(info);
|
||||
E2D_LOG_DEBUG("Watching shader: {} ({} files)", shaderName, filePaths.size());
|
||||
}
|
||||
|
||||
void ShaderManager::unwatch(const std::string& shaderName) {
|
||||
auto it = watchMap_.find(shaderName);
|
||||
if (it != watchMap_.end()) {
|
||||
watchMap_.erase(it);
|
||||
E2D_LOG_DEBUG("Stopped watching shader: {}", shaderName);
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderManager::pollChanges() {
|
||||
auto now = static_cast<uint64_t>(
|
||||
std::chrono::system_clock::now().time_since_epoch().count());
|
||||
|
||||
for (auto& pair : watchMap_) {
|
||||
WatchInfo& info = pair.second;
|
||||
|
||||
for (const auto& filePath : info.filePaths) {
|
||||
uint64_t currentModTime = getFileModifiedTime(filePath);
|
||||
uint64_t lastModTime = info.modifiedTimes[filePath];
|
||||
|
||||
if (currentModTime != 0 && lastModTime != 0 && currentModTime != lastModTime) {
|
||||
info.modifiedTimes[filePath] = currentModTime;
|
||||
|
||||
FileChangeEvent event;
|
||||
event.filepath = filePath;
|
||||
event.type = FileChangeEvent::Type::Modified;
|
||||
event.timestamp = now;
|
||||
|
||||
E2D_LOG_DEBUG("Shader file changed: {}", filePath);
|
||||
|
||||
if (info.callback) {
|
||||
info.callback(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t ShaderManager::getFileModifiedTime(const std::string& filepath) {
|
||||
try {
|
||||
auto ftime = fs::last_write_time(filepath);
|
||||
auto sctp = std::chrono::time_point_cast<std::chrono::seconds>(
|
||||
ftime - fs::file_time_type::clock::now() + std::chrono::system_clock::now());
|
||||
return static_cast<uint64_t>(sctp.time_since_epoch().count());
|
||||
} catch (...) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -112,15 +112,10 @@ TextureRef TexturePool::load(const std::string& path, const Rect& region,
|
|||
cacheMisses_.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
// 获取渲染后端
|
||||
RenderBackend* backend = nullptr;
|
||||
if (scene_) {
|
||||
// 假设 Scene 有获取 RenderBackend 的方法
|
||||
// 这里需要根据实际接口调整
|
||||
backend = nullptr; // TODO: 从 Scene 获取 RenderBackend
|
||||
}
|
||||
RenderBackend* backend = backend_;
|
||||
|
||||
if (!backend) {
|
||||
E2D_LOG_ERROR("TexturePool: RenderBackend not available");
|
||||
E2D_LOG_ERROR("TexturePool: RenderBackend not available, call setRenderBackend() first");
|
||||
return TextureRef();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
#include <extra2d/modules/config_module.h>
|
||||
#include <extra2d/core/module_macros.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
ConfigModule::ConfigModule()
|
||||
: Module() {
|
||||
}
|
||||
|
||||
ConfigModule::~ConfigModule() {
|
||||
if (isInitialized()) {
|
||||
destroyModule();
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigModule::setupModule() {
|
||||
if (isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!configPath_.empty()) {
|
||||
if (!ConfigManager::instance().initialize(configPath_)) {
|
||||
if (!ConfigManager::instance().initialize()) {
|
||||
E2D_LOG_ERROR("Config module initialization failed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!ConfigManager::instance().initialize()) {
|
||||
E2D_LOG_ERROR("Config module initialization failed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!appConfig_.appName.empty()) {
|
||||
ConfigManager::instance().setAppConfig(appConfig_);
|
||||
}
|
||||
|
||||
setInitialized(true);
|
||||
E2D_LOG_INFO("Config module initialized");
|
||||
}
|
||||
|
||||
void ConfigModule::destroyModule() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Config module shutting down");
|
||||
ConfigManager::instance().shutdown();
|
||||
setInitialized(false);
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
||||
E2D_MODULE(ConfigModule, 0)
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
#include <extra2d/modules/input_module.h>
|
||||
#include <extra2d/core/module_macros.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
#include <extra2d/services/event_service.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
#include "../platform/backends/sdl2/sdl2_input.h"
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
InputModule::InputModule()
|
||||
: Module()
|
||||
, window_(nullptr)
|
||||
, input_(nullptr) {
|
||||
}
|
||||
|
||||
InputModule::~InputModule() {
|
||||
if (isInitialized()) {
|
||||
destroyModule();
|
||||
}
|
||||
}
|
||||
|
||||
void InputModule::setupModule() {
|
||||
if (isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果 window 还没设置,延迟初始化
|
||||
if (!window_) {
|
||||
E2D_LOG_INFO("Input module waiting for window");
|
||||
setInitialized(true);
|
||||
return;
|
||||
}
|
||||
|
||||
initializeWithWindow();
|
||||
}
|
||||
|
||||
void InputModule::initializeWithWindow() {
|
||||
if (!window_) {
|
||||
return;
|
||||
}
|
||||
|
||||
input_ = window_->input();
|
||||
if (!input_) {
|
||||
E2D_LOG_ERROR("Input interface not available from window");
|
||||
return;
|
||||
}
|
||||
|
||||
SDL2Input* sdl2Input = dynamic_cast<SDL2Input*>(input_);
|
||||
if (sdl2Input) {
|
||||
auto eventService = ServiceLocator::instance().getService<IEventService>();
|
||||
if (eventService) {
|
||||
sdl2Input->setEventCallback([eventService](const Event& event) {
|
||||
Event mutableEvent = event;
|
||||
eventService->dispatch(mutableEvent);
|
||||
});
|
||||
E2D_LOG_INFO("Input events connected to EventService");
|
||||
} else {
|
||||
E2D_LOG_WARN("EventService not available - input events will not be dispatched");
|
||||
}
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Input module initialized");
|
||||
E2D_LOG_INFO(" Deadzone: {}", config_.deadzone);
|
||||
E2D_LOG_INFO(" Mouse sensitivity: {}", config_.mouseSensitivity);
|
||||
E2D_LOG_INFO(" Vibration: {}", config_.enableVibration ? "enabled" : "disabled");
|
||||
}
|
||||
|
||||
void InputModule::setWindow(IWindow* window) {
|
||||
if (window_ == window) {
|
||||
return;
|
||||
}
|
||||
window_ = window;
|
||||
|
||||
// 如果模块已初始化但还没初始化输入,现在初始化
|
||||
if (isInitialized() && window_ && !input_) {
|
||||
initializeWithWindow();
|
||||
}
|
||||
}
|
||||
|
||||
void InputModule::destroyModule() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Input module shutting down");
|
||||
|
||||
input_ = nullptr;
|
||||
setInitialized(false);
|
||||
}
|
||||
|
||||
void InputModule::onUpdate(UpdateContext& ctx) {
|
||||
if (!isInitialized() || !input_) {
|
||||
ctx.next();
|
||||
return;
|
||||
}
|
||||
|
||||
input_->update();
|
||||
ctx.next();
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
||||
E2D_MODULE(InputModule, 30, "WindowModule")
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
#include <extra2d/modules/render_module.h>
|
||||
#include <extra2d/core/module_macros.h>
|
||||
#include <extra2d/graphics/opengl/gl_shader.h>
|
||||
#include <extra2d/graphics/shader_manager.h>
|
||||
#include <extra2d/platform/iwindow.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <algorithm>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
RenderModule::RenderModule()
|
||||
: Module()
|
||||
, window_(nullptr)
|
||||
, renderer_(nullptr) {
|
||||
}
|
||||
|
||||
RenderModule::~RenderModule() {
|
||||
if (isInitialized()) {
|
||||
destroyModule();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderModule::setupModule() {
|
||||
if (isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!config_.validate()) {
|
||||
E2D_LOG_ERROR("Invalid render config");
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果 window 还没设置,延迟初始化
|
||||
if (!window_) {
|
||||
E2D_LOG_INFO("Render module waiting for window");
|
||||
setInitialized(true);
|
||||
return;
|
||||
}
|
||||
|
||||
initializeWithWindow();
|
||||
}
|
||||
|
||||
void RenderModule::initializeWithWindow() {
|
||||
if (!window_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto shaderFactory = std::make_shared<GLShaderFactory>();
|
||||
if (!ShaderManager::getInstance().init(shaderFactory, "extra2d")) {
|
||||
E2D_LOG_WARN("Failed to initialize ShaderManager with default paths");
|
||||
}
|
||||
|
||||
if (!ShaderManager::getInstance().loadBuiltinShaders()) {
|
||||
E2D_LOG_WARN("Failed to load some builtin shaders");
|
||||
}
|
||||
|
||||
renderer_ = RenderBackend::create(config_.backend);
|
||||
if (!renderer_) {
|
||||
E2D_LOG_ERROR("Failed to create render backend");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!renderer_->init(window_)) {
|
||||
E2D_LOG_ERROR("Failed to initialize renderer");
|
||||
renderer_.reset();
|
||||
return;
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Render module initialized");
|
||||
}
|
||||
|
||||
void RenderModule::setWindow(IWindow* window) {
|
||||
if (window_ == window) {
|
||||
return;
|
||||
}
|
||||
window_ = window;
|
||||
|
||||
// 如果模块已初始化但还没初始化渲染器,现在初始化
|
||||
if (isInitialized() && window_ && !renderer_) {
|
||||
initializeWithWindow();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderModule::destroyModule() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (renderer_) {
|
||||
renderer_->shutdown();
|
||||
renderer_.reset();
|
||||
}
|
||||
|
||||
ShaderManager::getInstance().shutdown();
|
||||
|
||||
setInitialized(false);
|
||||
E2D_LOG_INFO("Render module shutdown");
|
||||
}
|
||||
|
||||
void RenderModule::beforeRender(RenderContext& ctx) {
|
||||
if (!isInitialized() || !renderer_) {
|
||||
ctx.next();
|
||||
return;
|
||||
}
|
||||
|
||||
renderer_->beginFrame(Color(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
ctx.next();
|
||||
}
|
||||
|
||||
void RenderModule::afterRender(RenderContext& ctx) {
|
||||
if (!isInitialized() || !window_) {
|
||||
ctx.next();
|
||||
return;
|
||||
}
|
||||
|
||||
window_->swap();
|
||||
ctx.next();
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
||||
E2D_MODULE(RenderModule, 40, "WindowModule")
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
#include <extra2d/modules/window_module.h>
|
||||
#include <extra2d/core/module_macros.h>
|
||||
#include <extra2d/platform/backend_factory.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <SDL.h>
|
||||
|
||||
#ifdef __SWITCH__
|
||||
#include <switch.h>
|
||||
#endif
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
WindowModule::WindowModule()
|
||||
: Module()
|
||||
, sdl2Initialized_(false) {
|
||||
}
|
||||
|
||||
WindowModule::~WindowModule() {
|
||||
if (isInitialized()) {
|
||||
destroyModule();
|
||||
}
|
||||
}
|
||||
|
||||
void WindowModule::setupModule() {
|
||||
if (isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef __SWITCH__
|
||||
windowConfig_.mode = WindowMode::Fullscreen;
|
||||
windowConfig_.resizable = false;
|
||||
windowConfig_.highDPI = false;
|
||||
E2D_LOG_INFO("Switch platform: forcing fullscreen mode");
|
||||
#endif
|
||||
|
||||
if (!initSDL2()) {
|
||||
return;
|
||||
}
|
||||
|
||||
extern void initSDL2Backend();
|
||||
initSDL2Backend();
|
||||
|
||||
if (!BackendFactory::has("sdl2")) {
|
||||
E2D_LOG_ERROR("SDL2 backend not registered!");
|
||||
shutdownSDL2();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!createWindow(windowConfig_)) {
|
||||
E2D_LOG_ERROR("Failed to create window");
|
||||
shutdownSDL2();
|
||||
return;
|
||||
}
|
||||
|
||||
setInitialized(true);
|
||||
E2D_LOG_INFO("Window module initialized");
|
||||
E2D_LOG_INFO(" Window: {}x{}", window_->width(), window_->height());
|
||||
E2D_LOG_INFO(" Backend: SDL2");
|
||||
E2D_LOG_INFO(" VSync: {}", windowConfig_.vsync);
|
||||
E2D_LOG_INFO(" Fullscreen: {}", windowConfig_.isFullscreen());
|
||||
}
|
||||
|
||||
void WindowModule::destroyModule() {
|
||||
if (!isInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Window module shutting down");
|
||||
|
||||
if (window_) {
|
||||
window_->destroy();
|
||||
window_.reset();
|
||||
}
|
||||
|
||||
shutdownSDL2();
|
||||
|
||||
setInitialized(false);
|
||||
}
|
||||
|
||||
bool WindowModule::initSDL2() {
|
||||
Uint32 initFlags = SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER;
|
||||
|
||||
#ifdef __SWITCH__
|
||||
initFlags |= SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER;
|
||||
#endif
|
||||
|
||||
if (SDL_Init(initFlags) != 0) {
|
||||
E2D_LOG_ERROR("Failed to initialize SDL2: {}", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
sdl2Initialized_ = true;
|
||||
E2D_LOG_INFO("SDL2 initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
void WindowModule::shutdownSDL2() {
|
||||
if (!sdl2Initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_Quit();
|
||||
sdl2Initialized_ = false;
|
||||
E2D_LOG_INFO("SDL2 shutdown");
|
||||
}
|
||||
|
||||
bool WindowModule::createWindow(const WindowConfigData& config) {
|
||||
window_ = BackendFactory::createWindow("sdl2");
|
||||
if (!window_) {
|
||||
E2D_LOG_ERROR("Failed to create SDL2 window");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!window_->create(config)) {
|
||||
E2D_LOG_ERROR("Failed to create window with specified config");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
||||
E2D_MODULE(WindowModule, 20, "ConfigModule")
|
||||
|
|
@ -12,18 +12,16 @@ void BackendFactory::reg(const std::string& name, WindowFn win, InputFn in) {
|
|||
}
|
||||
|
||||
UniquePtr<IWindow> BackendFactory::createWindow(const std::string& name) {
|
||||
auto& reg = registry();
|
||||
auto it = reg.find(name);
|
||||
if (it != reg.end() && it->second.windowFn) {
|
||||
auto it = registry().find(name);
|
||||
if (it != registry().end() && it->second.windowFn) {
|
||||
return it->second.windowFn();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UniquePtr<IInput> BackendFactory::createInput(const std::string& name) {
|
||||
auto& reg = registry();
|
||||
auto it = reg.find(name);
|
||||
if (it != reg.end() && it->second.inputFn) {
|
||||
auto it = registry().find(name);
|
||||
if (it != registry().end() && it->second.inputFn) {
|
||||
return it->second.inputFn();
|
||||
}
|
||||
return nullptr;
|
||||
|
|
@ -41,4 +39,4 @@ bool BackendFactory::has(const std::string& name) {
|
|||
return registry().find(name) != registry().end();
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@
|
|||
|
||||
namespace extra2d {
|
||||
|
||||
SDL2Input::SDL2Input() {
|
||||
SDL2Input::SDL2Input()
|
||||
: initialized_(false) {
|
||||
keyCurrent_.fill(false);
|
||||
keyPrevious_.fill(false);
|
||||
mouseCurrent_.fill(false);
|
||||
|
|
@ -18,6 +19,8 @@ SDL2Input::~SDL2Input() {
|
|||
}
|
||||
|
||||
void SDL2Input::init() {
|
||||
if (initialized_) return;
|
||||
|
||||
E2D_LOG_INFO("SDL2Input initialized");
|
||||
|
||||
if (SDL_Init(SDL_INIT_GAMECONTROLLER) != 0) {
|
||||
|
|
@ -25,10 +28,14 @@ void SDL2Input::init() {
|
|||
}
|
||||
|
||||
openGamepad();
|
||||
initialized_ = true;
|
||||
}
|
||||
|
||||
void SDL2Input::shutdown() {
|
||||
if (!initialized_) return;
|
||||
|
||||
closeGamepad();
|
||||
initialized_ = false;
|
||||
E2D_LOG_INFO("SDL2Input shutdown");
|
||||
}
|
||||
|
||||
|
|
@ -40,6 +47,8 @@ void SDL2Input::update() {
|
|||
scrollDelta_ = 0.0f;
|
||||
mouseDelta_ = Vec2{0.0f, 0.0f};
|
||||
|
||||
updateKeyboard();
|
||||
updateMouse();
|
||||
updateGamepad();
|
||||
}
|
||||
|
||||
|
|
@ -49,64 +58,6 @@ void SDL2Input::setEventCallback(EventCallback callback) {
|
|||
|
||||
void SDL2Input::handleSDLEvent(const SDL_Event& event) {
|
||||
switch (event.type) {
|
||||
case SDL_KEYDOWN: {
|
||||
int key = event.key.keysym.scancode;
|
||||
if (key >= 0 && key < static_cast<int>(Key::Count)) {
|
||||
if (!keyCurrent_[key]) {
|
||||
keyCurrent_[key] = true;
|
||||
|
||||
Event e = Event::createKeyPress(
|
||||
event.key.keysym.sym,
|
||||
event.key.keysym.scancode,
|
||||
event.key.keysym.mod
|
||||
);
|
||||
dispatchEvent(e);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_KEYUP: {
|
||||
int key = event.key.keysym.scancode;
|
||||
if (key >= 0 && key < static_cast<int>(Key::Count)) {
|
||||
keyCurrent_[key] = false;
|
||||
|
||||
Event e = Event::createKeyRelease(
|
||||
event.key.keysym.sym,
|
||||
event.key.keysym.scancode,
|
||||
event.key.keysym.mod
|
||||
);
|
||||
dispatchEvent(e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_MOUSEBUTTONDOWN: {
|
||||
int btn = event.button.button - 1;
|
||||
if (btn >= 0 && btn < static_cast<int>(Mouse::Count)) {
|
||||
mouseCurrent_[btn] = true;
|
||||
|
||||
Vec2 pos{static_cast<float>(event.button.x),
|
||||
static_cast<float>(event.button.y)};
|
||||
Event e = Event::createMouseButtonPress(btn, 0, pos);
|
||||
dispatchEvent(e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_MOUSEBUTTONUP: {
|
||||
int btn = event.button.button - 1;
|
||||
if (btn >= 0 && btn < static_cast<int>(Mouse::Count)) {
|
||||
mouseCurrent_[btn] = false;
|
||||
|
||||
Vec2 pos{static_cast<float>(event.button.x),
|
||||
static_cast<float>(event.button.y)};
|
||||
Event e = Event::createMouseButtonRelease(btn, 0, pos);
|
||||
dispatchEvent(e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_MOUSEMOTION: {
|
||||
Vec2 newPos{static_cast<float>(event.motion.x),
|
||||
static_cast<float>(event.motion.y)};
|
||||
|
|
@ -152,12 +103,12 @@ void SDL2Input::handleSDLEvent(const SDL_Event& event) {
|
|||
if (btn >= 0 && btn < static_cast<int>(Gamepad::Count)) {
|
||||
gamepadCurrent_[btn] = true;
|
||||
|
||||
GamepadButtonEvent btnEvent;
|
||||
GamepadEvent btnEvent;
|
||||
btnEvent.gamepadId = gamepadIndex_;
|
||||
btnEvent.button = btn;
|
||||
|
||||
Event e;
|
||||
e.type = EventType::GamepadButtonPressed;
|
||||
e.type = EventType::GamepadPress;
|
||||
e.data = btnEvent;
|
||||
dispatchEvent(e);
|
||||
}
|
||||
|
|
@ -170,12 +121,12 @@ void SDL2Input::handleSDLEvent(const SDL_Event& event) {
|
|||
if (btn >= 0 && btn < static_cast<int>(Gamepad::Count)) {
|
||||
gamepadCurrent_[btn] = false;
|
||||
|
||||
GamepadButtonEvent btnEvent;
|
||||
GamepadEvent btnEvent;
|
||||
btnEvent.gamepadId = gamepadIndex_;
|
||||
btnEvent.button = btn;
|
||||
|
||||
Event e;
|
||||
e.type = EventType::GamepadButtonReleased;
|
||||
e.type = EventType::GamepadRelease;
|
||||
e.data = btnEvent;
|
||||
dispatchEvent(e);
|
||||
}
|
||||
|
|
@ -194,48 +145,51 @@ void SDL2Input::dispatchEvent(const Event& event) {
|
|||
}
|
||||
|
||||
bool SDL2Input::down(Key key) const {
|
||||
size_t idx = static_cast<size_t>(key);
|
||||
if (idx < keyCurrent_.size()) {
|
||||
int idx = static_cast<int>(key);
|
||||
if (idx >= 0 && idx < static_cast<int>(Key::Count)) {
|
||||
return keyCurrent_[idx];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SDL2Input::pressed(Key key) const {
|
||||
size_t idx = static_cast<size_t>(key);
|
||||
if (idx < keyCurrent_.size()) {
|
||||
int idx = static_cast<int>(key);
|
||||
if (idx >= 0 && idx < static_cast<int>(Key::Count)) {
|
||||
return keyCurrent_[idx] && !keyPrevious_[idx];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SDL2Input::released(Key key) const {
|
||||
size_t idx = static_cast<size_t>(key);
|
||||
if (idx < keyCurrent_.size()) {
|
||||
int idx = static_cast<int>(key);
|
||||
if (idx >= 0 && idx < static_cast<int>(Key::Count)) {
|
||||
return !keyCurrent_[idx] && keyPrevious_[idx];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SDL2Input::down(Mouse btn) const {
|
||||
size_t idx = static_cast<size_t>(btn);
|
||||
if (idx < mouseCurrent_.size()) {
|
||||
int sdlBtn = static_cast<int>(btn);
|
||||
int idx = sdlBtn - 1;
|
||||
if (idx >= 0 && idx < static_cast<int>(Mouse::Count)) {
|
||||
return mouseCurrent_[idx];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SDL2Input::pressed(Mouse btn) const {
|
||||
size_t idx = static_cast<size_t>(btn);
|
||||
if (idx < mouseCurrent_.size()) {
|
||||
int sdlBtn = static_cast<int>(btn);
|
||||
int idx = sdlBtn - 1;
|
||||
if (idx >= 0 && idx < static_cast<int>(Mouse::Count)) {
|
||||
return mouseCurrent_[idx] && !mousePrevious_[idx];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SDL2Input::released(Mouse btn) const {
|
||||
size_t idx = static_cast<size_t>(btn);
|
||||
if (idx < mouseCurrent_.size()) {
|
||||
int sdlBtn = static_cast<int>(btn);
|
||||
int idx = sdlBtn - 1;
|
||||
if (idx >= 0 && idx < static_cast<int>(Mouse::Count)) {
|
||||
return !mouseCurrent_[idx] && mousePrevious_[idx];
|
||||
}
|
||||
return false;
|
||||
|
|
@ -266,24 +220,24 @@ bool SDL2Input::gamepad() const {
|
|||
}
|
||||
|
||||
bool SDL2Input::down(Gamepad btn) const {
|
||||
size_t idx = static_cast<size_t>(btn);
|
||||
if (idx < gamepadCurrent_.size()) {
|
||||
int idx = static_cast<int>(btn);
|
||||
if (idx >= 0 && idx < static_cast<int>(Gamepad::Count)) {
|
||||
return gamepadCurrent_[idx];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SDL2Input::pressed(Gamepad btn) const {
|
||||
size_t idx = static_cast<size_t>(btn);
|
||||
if (idx < gamepadCurrent_.size()) {
|
||||
int idx = static_cast<int>(btn);
|
||||
if (idx >= 0 && idx < static_cast<int>(Gamepad::Count)) {
|
||||
return gamepadCurrent_[idx] && !gamepadPrevious_[idx];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SDL2Input::released(Gamepad btn) const {
|
||||
size_t idx = static_cast<size_t>(btn);
|
||||
if (idx < gamepadCurrent_.size()) {
|
||||
int idx = static_cast<int>(btn);
|
||||
if (idx >= 0 && idx < static_cast<int>(Gamepad::Count)) {
|
||||
return !gamepadCurrent_[idx] && gamepadPrevious_[idx];
|
||||
}
|
||||
return false;
|
||||
|
|
@ -332,12 +286,65 @@ TouchPoint SDL2Input::touchPoint(int index) const {
|
|||
}
|
||||
|
||||
void SDL2Input::updateKeyboard() {
|
||||
int numKeys = 0;
|
||||
const Uint8* state = SDL_GetKeyboardState(&numKeys);
|
||||
|
||||
for (int i = 0; i < static_cast<int>(Key::Count) && i < numKeys; ++i) {
|
||||
bool currentState = state[i] != 0;
|
||||
|
||||
if (currentState != keyCurrent_[i]) {
|
||||
if (currentState && !keyCurrent_[i]) {
|
||||
Event e = Event::createKeyPress(
|
||||
SDL_GetKeyFromScancode(static_cast<SDL_Scancode>(i)),
|
||||
i,
|
||||
SDL_GetModState()
|
||||
);
|
||||
dispatchEvent(e);
|
||||
} else if (!currentState && keyCurrent_[i]) {
|
||||
Event e = Event::createKeyRelease(
|
||||
SDL_GetKeyFromScancode(static_cast<SDL_Scancode>(i)),
|
||||
i,
|
||||
SDL_GetModState()
|
||||
);
|
||||
dispatchEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
keyCurrent_[i] = currentState;
|
||||
}
|
||||
}
|
||||
|
||||
void SDL2Input::updateMouse() {
|
||||
int x = 0, y = 0;
|
||||
SDL_GetMouseState(&x, &y);
|
||||
mousePos_ = Vec2{static_cast<float>(x), static_cast<float>(y)};
|
||||
Uint32 state = SDL_GetMouseState(&x, &y);
|
||||
|
||||
Vec2 newPos{static_cast<float>(x), static_cast<float>(y)};
|
||||
mouseDelta_ = newPos - mousePos_;
|
||||
mousePos_ = newPos;
|
||||
|
||||
static const int mouseButtons[] = {
|
||||
SDL_BUTTON_LEFT,
|
||||
SDL_BUTTON_MIDDLE,
|
||||
SDL_BUTTON_RIGHT,
|
||||
SDL_BUTTON_X1,
|
||||
SDL_BUTTON_X2
|
||||
};
|
||||
|
||||
for (int i = 0; i < static_cast<int>(Mouse::Count); ++i) {
|
||||
bool currentState = (state & SDL_BUTTON(mouseButtons[i])) != 0;
|
||||
|
||||
if (currentState != mouseCurrent_[i]) {
|
||||
if (currentState && !mouseCurrent_[i]) {
|
||||
Event e = Event::createMousePress(mouseButtons[i], 0, mousePos_);
|
||||
dispatchEvent(e);
|
||||
} else if (!currentState && mouseCurrent_[i]) {
|
||||
Event e = Event::createMouseRelease(mouseButtons[i], 0, mousePos_);
|
||||
dispatchEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
mouseCurrent_[i] = currentState;
|
||||
}
|
||||
}
|
||||
|
||||
void SDL2Input::updateGamepad() {
|
||||
|
|
@ -399,35 +406,11 @@ int SDL2Input::keyToSDL(Key key) {
|
|||
}
|
||||
|
||||
int SDL2Input::mouseToSDL(Mouse btn) {
|
||||
switch (btn) {
|
||||
case Mouse::Left: return SDL_BUTTON_LEFT;
|
||||
case Mouse::Middle: return SDL_BUTTON_MIDDLE;
|
||||
case Mouse::Right: return SDL_BUTTON_RIGHT;
|
||||
case Mouse::X1: return SDL_BUTTON_X1;
|
||||
case Mouse::X2: return SDL_BUTTON_X2;
|
||||
default: return 0;
|
||||
}
|
||||
return static_cast<int>(btn);
|
||||
}
|
||||
|
||||
int SDL2Input::gamepadToSDL(Gamepad btn) {
|
||||
switch (btn) {
|
||||
case Gamepad::A: return SDL_CONTROLLER_BUTTON_A;
|
||||
case Gamepad::B: return SDL_CONTROLLER_BUTTON_B;
|
||||
case Gamepad::X: return SDL_CONTROLLER_BUTTON_X;
|
||||
case Gamepad::Y: return SDL_CONTROLLER_BUTTON_Y;
|
||||
case Gamepad::Back: return SDL_CONTROLLER_BUTTON_BACK;
|
||||
case Gamepad::Start: return SDL_CONTROLLER_BUTTON_START;
|
||||
case Gamepad::LStick: return SDL_CONTROLLER_BUTTON_LEFTSTICK;
|
||||
case Gamepad::RStick: return SDL_CONTROLLER_BUTTON_RIGHTSTICK;
|
||||
case Gamepad::LB: return SDL_CONTROLLER_BUTTON_LEFTSHOULDER;
|
||||
case Gamepad::RB: return SDL_CONTROLLER_BUTTON_RIGHTSHOULDER;
|
||||
case Gamepad::DUp: return SDL_CONTROLLER_BUTTON_DPAD_UP;
|
||||
case Gamepad::DDown: return SDL_CONTROLLER_BUTTON_DPAD_DOWN;
|
||||
case Gamepad::DLeft: return SDL_CONTROLLER_BUTTON_DPAD_LEFT;
|
||||
case Gamepad::DRight: return SDL_CONTROLLER_BUTTON_DPAD_RIGHT;
|
||||
case Gamepad::Guide: return SDL_CONTROLLER_BUTTON_GUIDE;
|
||||
default: return 0;
|
||||
}
|
||||
return static_cast<int>(btn);
|
||||
}
|
||||
|
||||
Key SDL2Input::sdlToKey(int sdlKey) {
|
||||
|
|
@ -438,14 +421,7 @@ Key SDL2Input::sdlToKey(int sdlKey) {
|
|||
}
|
||||
|
||||
Mouse SDL2Input::sdlToMouse(int sdlButton) {
|
||||
switch (sdlButton) {
|
||||
case SDL_BUTTON_LEFT: return Mouse::Left;
|
||||
case SDL_BUTTON_MIDDLE: return Mouse::Middle;
|
||||
case SDL_BUTTON_RIGHT: return Mouse::Right;
|
||||
case SDL_BUTTON_X1: return Mouse::X1;
|
||||
case SDL_BUTTON_X2: return Mouse::X2;
|
||||
default: return Mouse::Count;
|
||||
}
|
||||
return static_cast<Mouse>(sdlButton);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ private:
|
|||
float rightTrigger_ = 0.0f;
|
||||
float deadzone_ = 0.15f;
|
||||
|
||||
bool initialized_ = false;
|
||||
EventCallback eventCallback_;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,49 +0,0 @@
|
|||
#include <extra2d/platform/input_module.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
#include <extra2d/core/registry.h>
|
||||
#include <extra2d/services/event_service.h>
|
||||
#include <extra2d/platform/window_module.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
InputModule::InputModule(const Cfg& cfg) : cfg_(cfg) {}
|
||||
|
||||
InputModule::~InputModule() {
|
||||
if (initialized_) {
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
bool InputModule::init() {
|
||||
if (initialized_) return true;
|
||||
|
||||
// 获取WindowModule依赖
|
||||
auto* winMod = Registry::instance().get<WindowModule>();
|
||||
if (!winMod || !winMod->win()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取输入接口
|
||||
input_ = winMod->win()->input();
|
||||
if (!input_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
initialized_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void InputModule::shutdown() {
|
||||
if (!initialized_) return;
|
||||
|
||||
input_ = nullptr;
|
||||
initialized_ = false;
|
||||
}
|
||||
|
||||
void InputModule::update() {
|
||||
if (initialized_ && input_) {
|
||||
input_->update();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
#include <extra2d/platform/window_module.h>
|
||||
#include <extra2d/platform/backend_factory.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <SDL.h>
|
||||
|
||||
#ifdef __SWITCH__
|
||||
#include <switch.h>
|
||||
#endif
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// 前向声明 SDL2 后端初始化函数
|
||||
void initSDL2Backend();
|
||||
|
||||
WindowModule::WindowModule(const Cfg& cfg) : cfg_(cfg) {}
|
||||
|
||||
WindowModule::~WindowModule() {
|
||||
if (initialized_) {
|
||||
shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
bool WindowModule::init() {
|
||||
if (initialized_) return true;
|
||||
|
||||
#ifdef __SWITCH__
|
||||
cfg_.mode = WindowMode::Fullscreen;
|
||||
#endif
|
||||
|
||||
// 初始化SDL后端(注册到工厂)
|
||||
initSDL2Backend();
|
||||
|
||||
// 初始化SDL
|
||||
Uint32 flags = SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER;
|
||||
#ifdef __SWITCH__
|
||||
flags |= SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER;
|
||||
#endif
|
||||
|
||||
if (SDL_Init(flags) != 0) {
|
||||
E2D_LOG_ERROR("SDL_Init failed: {}", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
sdlInited_ = true;
|
||||
E2D_LOG_INFO("SDL initialized successfully");
|
||||
|
||||
// 创建窗口配置
|
||||
WindowConfigData winCfg;
|
||||
winCfg.title = cfg_.title;
|
||||
winCfg.width = cfg_.w;
|
||||
winCfg.height = cfg_.h;
|
||||
winCfg.mode = cfg_.mode;
|
||||
winCfg.vsync = cfg_.vsync;
|
||||
|
||||
E2D_LOG_INFO("Creating window with size {}x{}", cfg_.w, cfg_.h);
|
||||
|
||||
// 创建窗口(使用配置的后端)
|
||||
win_ = BackendFactory::createWindow(cfg_.backend);
|
||||
if (!win_) {
|
||||
E2D_LOG_ERROR("Failed to create window backend");
|
||||
shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!win_->create(winCfg)) {
|
||||
E2D_LOG_ERROR("Failed to create window");
|
||||
shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Window created successfully");
|
||||
initialized_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void WindowModule::shutdown() {
|
||||
if (!initialized_) return;
|
||||
|
||||
if (win_) {
|
||||
win_->destroy();
|
||||
win_.reset();
|
||||
}
|
||||
|
||||
if (sdlInited_) {
|
||||
SDL_Quit();
|
||||
sdlInited_ = false;
|
||||
}
|
||||
|
||||
initialized_ = false;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -3,6 +3,8 @@
|
|||
#include <extra2d/graphics/core/render_command.h>
|
||||
#include <extra2d/scene/node.h>
|
||||
#include <extra2d/scene/scene.h>
|
||||
#include <extra2d/services/event_service.h>
|
||||
#include <extra2d/core/service_locator.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
|
@ -650,4 +652,40 @@ void Node::collectRenderCommands(std::vector<RenderCommand> &commands,
|
|||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 事件系统便捷方法
|
||||
// ============================================================================
|
||||
|
||||
ListenerId Node::addListener(EventType type, EventDispatcher::EventCallback callback) {
|
||||
auto eventService = ServiceLocator::instance().getService<IEventService>();
|
||||
if (eventService) {
|
||||
return eventService->addListener(type, std::move(callback));
|
||||
}
|
||||
return eventDispatcher_.addListener(type, std::move(callback));
|
||||
}
|
||||
|
||||
void Node::removeListener(ListenerId id) {
|
||||
auto eventService = ServiceLocator::instance().getService<IEventService>();
|
||||
if (eventService) {
|
||||
eventService->removeListener(id);
|
||||
}
|
||||
eventDispatcher_.removeListener(id);
|
||||
}
|
||||
|
||||
void Node::removeAllListeners(EventType type) {
|
||||
auto eventService = ServiceLocator::instance().getService<IEventService>();
|
||||
if (eventService) {
|
||||
eventService->removeAllListeners(type);
|
||||
}
|
||||
eventDispatcher_.removeAllListeners(type);
|
||||
}
|
||||
|
||||
void Node::removeAllListeners() {
|
||||
auto eventService = ServiceLocator::instance().getService<IEventService>();
|
||||
if (eventService) {
|
||||
eventService->removeAllListeners();
|
||||
}
|
||||
eventDispatcher_.removeAllListeners();
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -795,12 +795,12 @@ void SceneManager::dispatchPointerEvents(Scene &scene) {
|
|||
if (input->pressed(Mouse::Left)) {
|
||||
captureTarget_ = hoverTarget_;
|
||||
if (captureTarget_) {
|
||||
Event evt = Event::createMouseButtonPress(static_cast<int>(Mouse::Left),
|
||||
0, worldPos);
|
||||
Event evt =
|
||||
Event::createMousePress(static_cast<int>(Mouse::Left), 0, worldPos);
|
||||
dispatchToNode(captureTarget_, evt);
|
||||
|
||||
Event pressed;
|
||||
pressed.type = EventType::UIPressed;
|
||||
pressed.type = EventType::UIPress;
|
||||
pressed.data = CustomEvent{0, captureTarget_};
|
||||
dispatchToNode(captureTarget_, pressed);
|
||||
}
|
||||
|
|
@ -809,19 +809,19 @@ void SceneManager::dispatchPointerEvents(Scene &scene) {
|
|||
if (input->released(Mouse::Left)) {
|
||||
Node *target = captureTarget_ ? captureTarget_ : hoverTarget_;
|
||||
if (target) {
|
||||
Event evt = Event::createMouseButtonRelease(static_cast<int>(Mouse::Left),
|
||||
0, worldPos);
|
||||
Event evt =
|
||||
Event::createMouseRelease(static_cast<int>(Mouse::Left), 0, worldPos);
|
||||
dispatchToNode(target, evt);
|
||||
|
||||
Event released;
|
||||
released.type = EventType::UIReleased;
|
||||
released.type = EventType::UIRelease;
|
||||
released.data = CustomEvent{0, target};
|
||||
dispatchToNode(target, released);
|
||||
}
|
||||
|
||||
if (captureTarget_ && captureTarget_ == hoverTarget_) {
|
||||
Event clicked;
|
||||
clicked.type = EventType::UIClicked;
|
||||
clicked.type = EventType::UIClick;
|
||||
clicked.data = CustomEvent{0, captureTarget_};
|
||||
dispatchToNode(captureTarget_, clicked);
|
||||
}
|
||||
|
|
@ -832,7 +832,6 @@ void SceneManager::dispatchPointerEvents(Scene &scene) {
|
|||
lastPointerWorld_ = worldPos;
|
||||
}
|
||||
|
||||
void SceneManager::doSceneSwitch() {
|
||||
}
|
||||
void SceneManager::doSceneSwitch() {}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -0,0 +1,610 @@
|
|||
# Extra2D 快速入门指南
|
||||
|
||||
本指南将帮助您快速上手 Extra2D 游戏引擎,从安装到创建您的第一个游戏。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [环境准备](#环境准备)
|
||||
2. [创建项目](#创建项目)
|
||||
3. [基础概念](#基础概念)
|
||||
4. [创建场景](#创建场景)
|
||||
5. [添加节点](#添加节点)
|
||||
6. [处理输入](#处理输入)
|
||||
7. [自定义模块](#自定义模块)
|
||||
8. [完整示例](#完整示例)
|
||||
|
||||
---
|
||||
|
||||
## 环境准备
|
||||
|
||||
### 安装 xmake
|
||||
|
||||
**Windows (PowerShell):**
|
||||
```powershell
|
||||
Invoke-Expression (Invoke-WebRequest 'https://xmake.io/psget.text' -UseBasicParsing).Content
|
||||
```
|
||||
|
||||
**macOS:**
|
||||
```bash
|
||||
brew install xmake
|
||||
```
|
||||
|
||||
**Linux:**
|
||||
```bash
|
||||
sudo add-apt-repository ppa:xmake-io/xmake
|
||||
sudo apt update
|
||||
sudo apt install xmake
|
||||
```
|
||||
|
||||
### 克隆项目
|
||||
|
||||
```bash
|
||||
git clone https://github.com/ChestnutYueyue/extra2d.git
|
||||
cd extra2d
|
||||
```
|
||||
|
||||
### 构建项目
|
||||
|
||||
```bash
|
||||
# 配置项目
|
||||
xmake f -p mingw -a x86_64 -m release -y
|
||||
|
||||
# 构建
|
||||
xmake build
|
||||
|
||||
# 运行示例
|
||||
xmake run demo_basic
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 创建项目
|
||||
|
||||
### 最小示例
|
||||
|
||||
创建一个 `main.cpp` 文件:
|
||||
|
||||
```cpp
|
||||
#include <extra2d/extra2d.h>
|
||||
|
||||
using namespace extra2d;
|
||||
|
||||
int main() {
|
||||
// 1. 配置应用
|
||||
AppConfig config;
|
||||
config.appName = "My First Game";
|
||||
config.appVersion = "1.0.0";
|
||||
|
||||
// 2. 获取应用实例
|
||||
Application& app = Application::get();
|
||||
|
||||
// 3. 初始化
|
||||
if (!app.init(config)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 4. 创建场景
|
||||
auto scene = Scene::create();
|
||||
scene->setBackgroundColor(Color(0.1f, 0.1f, 0.2f, 1.0f));
|
||||
|
||||
// 5. 进入场景
|
||||
app.enterScene(scene);
|
||||
|
||||
// 6. 运行游戏循环
|
||||
app.run();
|
||||
|
||||
// 7. 清理
|
||||
app.shutdown();
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 基础概念
|
||||
|
||||
### 模块系统
|
||||
|
||||
Extra2D 使用模块化架构,通过 `Application::use()` 注册模块:
|
||||
|
||||
```cpp
|
||||
// 内置模块会自动注册,您也可以自定义
|
||||
MyModule myModule;
|
||||
app.use(myModule);
|
||||
```
|
||||
|
||||
**内置模块优先级:**
|
||||
|
||||
| 模块 | 优先级 | 说明 |
|
||||
|------|--------|------|
|
||||
| Logger | -1 | 日志系统 |
|
||||
| Config | 0 | 配置管理 |
|
||||
| Platform | 10 | 平台检测 |
|
||||
| Window | 20 | 窗口管理 |
|
||||
| Input | 30 | 输入系统 |
|
||||
| Render | 40 | 渲染系统 |
|
||||
|
||||
### 服务系统
|
||||
|
||||
服务提供运行时功能,通过 `Application` 的便捷方法访问:
|
||||
|
||||
```cpp
|
||||
auto sceneService = app.scenes(); // 场景管理
|
||||
auto timerService = app.timers(); // 计时器
|
||||
auto eventService = app.events(); // 事件分发
|
||||
auto cameraService = app.camera(); // 相机系统
|
||||
```
|
||||
|
||||
### 场景图
|
||||
|
||||
场景图是一个树形结构,由 `Scene` 和 `Node` 组成:
|
||||
|
||||
```
|
||||
Scene (根节点)
|
||||
├── Node (父节点)
|
||||
│ ├── Node (子节点)
|
||||
│ └── ShapeNode (形状节点)
|
||||
└── Sprite (精灵)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 创建场景
|
||||
|
||||
### 基本场景
|
||||
|
||||
```cpp
|
||||
class MyScene : public Scene {
|
||||
public:
|
||||
static Ptr<MyScene> create() {
|
||||
return makeShared<MyScene>();
|
||||
}
|
||||
|
||||
void onEnter() override {
|
||||
Scene::onEnter();
|
||||
|
||||
// 设置背景颜色
|
||||
setBackgroundColor(Color(0.1f, 0.1f, 0.2f, 1.0f));
|
||||
|
||||
// 在这里添加节点和设置游戏逻辑
|
||||
}
|
||||
|
||||
void onUpdate(float dt) override {
|
||||
Scene::onUpdate(dt);
|
||||
|
||||
// 每帧更新逻辑
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 使用场景
|
||||
|
||||
```cpp
|
||||
auto scene = MyScene::create();
|
||||
app.enterScene(scene);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 添加节点
|
||||
|
||||
### 创建形状节点
|
||||
|
||||
```cpp
|
||||
// 矩形
|
||||
auto rect = ShapeNode::createFilledRect(
|
||||
Rect(0, 0, 100, 100), // 位置和大小
|
||||
Color(1.0f, 0.4f, 0.4f, 1.0f) // 颜色
|
||||
);
|
||||
scene->addChild(rect);
|
||||
|
||||
// 圆形
|
||||
auto circle = ShapeNode::createFilledCircle(
|
||||
Vec2(50, 50), // 圆心
|
||||
30, // 半径
|
||||
Color(0.4f, 0.4f, 1.0f, 1.0f) // 颜色
|
||||
);
|
||||
scene->addChild(circle);
|
||||
|
||||
// 三角形
|
||||
auto triangle = ShapeNode::createFilledTriangle(
|
||||
Vec2(50, 0), // 顶点1
|
||||
Vec2(0, 100), // 顶点2
|
||||
Vec2(100, 100), // 顶点3
|
||||
Color(0.4f, 1.0f, 0.4f, 1.0f) // 颜色
|
||||
);
|
||||
scene->addChild(triangle);
|
||||
|
||||
// 线段
|
||||
auto line = ShapeNode::createLine(
|
||||
Vec2(0, 0), // 起点
|
||||
Vec2(100, 100), // 终点
|
||||
Color(1.0f, 1.0f, 1.0f, 1.0f), // 颜色
|
||||
2.0f // 线宽
|
||||
);
|
||||
scene->addChild(line);
|
||||
```
|
||||
|
||||
### 节点变换
|
||||
|
||||
```cpp
|
||||
// 位置
|
||||
node->setPos(100, 200);
|
||||
Vec2 pos = node->getPosition();
|
||||
|
||||
// 旋转(角度)
|
||||
node->setRotation(45);
|
||||
float rotation = node->getRotation();
|
||||
|
||||
// 缩放
|
||||
node->setScale(2.0f); // 统一缩放
|
||||
node->setScale(2.0f, 1.5f); // 分别设置 X/Y
|
||||
Vec2 scale = node->getScale();
|
||||
|
||||
// 锚点(变换中心)
|
||||
node->setAnchor(0.5f, 0.5f); // 中心(默认)
|
||||
|
||||
// 透明度
|
||||
node->setOpacity(0.5f); // 0.0 - 1.0
|
||||
|
||||
// 可见性
|
||||
node->setVisible(false);
|
||||
|
||||
// Z 序(渲染顺序)
|
||||
node->setZOrder(10);
|
||||
```
|
||||
|
||||
### 节点层级
|
||||
|
||||
```cpp
|
||||
// 添加子节点
|
||||
parent->addChild(child);
|
||||
|
||||
// 移除子节点
|
||||
parent->removeChild(child);
|
||||
|
||||
// 从父节点分离
|
||||
child->detach();
|
||||
|
||||
// 查找子节点
|
||||
Ptr<Node> found = parent->findChild("nodeName");
|
||||
Ptr<Node> foundByTag = parent->findChildByTag(1);
|
||||
|
||||
// 清除所有子节点
|
||||
parent->clearChildren();
|
||||
```
|
||||
|
||||
### 变换继承
|
||||
|
||||
子节点会继承父节点的变换:
|
||||
|
||||
```cpp
|
||||
auto parent = makeShared<Node>();
|
||||
parent->setPos(400, 300);
|
||||
parent->setRotation(30); // 旋转 30 度
|
||||
|
||||
auto child = ShapeNode::createFilledRect(
|
||||
Rect(-25, -25, 50, 50),
|
||||
Color(1.0f, 0.0f, 0.0f, 1.0f)
|
||||
);
|
||||
child->setPos(100, 0); // 相对于父节点
|
||||
|
||||
parent->addChild(child);
|
||||
scene->addChild(parent);
|
||||
|
||||
// child 会随 parent 一起旋转
|
||||
// child 的世界位置约为 (486.6, 350)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 处理输入
|
||||
|
||||
### 在场景中处理事件
|
||||
|
||||
```cpp
|
||||
class MyScene : public Scene {
|
||||
public:
|
||||
void onEnter() override {
|
||||
Scene::onEnter();
|
||||
|
||||
// 键盘事件
|
||||
addListener(EventType::KeyPress, [](Event& e) {
|
||||
auto& key = std::get<KeyEvent>(e.data);
|
||||
|
||||
if (key.scancode == static_cast<int>(Key::Escape)) {
|
||||
e.handled = true;
|
||||
Application::get().quit();
|
||||
}
|
||||
|
||||
if (key.scancode == static_cast<int>(Key::Space)) {
|
||||
E2D_LOG_INFO("Space pressed!");
|
||||
}
|
||||
});
|
||||
|
||||
// 鼠标事件
|
||||
addListener(EventType::MousePress, [](Event& e) {
|
||||
auto& mouse = std::get<MouseEvent>(e.data);
|
||||
E2D_LOG_INFO("Click at ({}, {})", mouse.position.x, mouse.position.y);
|
||||
});
|
||||
|
||||
// 手柄事件
|
||||
addListener(EventType::GamepadPress, [](Event& e) {
|
||||
auto& gamepad = std::get<GamepadEvent>(e.data);
|
||||
E2D_LOG_INFO("Gamepad button: {}", gamepad.button);
|
||||
});
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 实时输入查询
|
||||
|
||||
```cpp
|
||||
void onUpdate(float dt) override {
|
||||
Scene::onUpdate(dt);
|
||||
|
||||
auto& input = Application::get().input();
|
||||
|
||||
// 键盘
|
||||
if (input.down(Key::W)) {
|
||||
// W 键被按住
|
||||
}
|
||||
if (input.pressed(Key::Space)) {
|
||||
// 空格键刚按下
|
||||
}
|
||||
if (input.released(Key::Space)) {
|
||||
// 空格键刚释放
|
||||
}
|
||||
|
||||
// 鼠标
|
||||
Vec2 mousePos = input.mouse();
|
||||
if (input.down(Mouse::Left)) {
|
||||
// 左键被按住
|
||||
}
|
||||
|
||||
// 手柄
|
||||
if (input.gamepad()) {
|
||||
Vec2 leftStick = input.leftStick();
|
||||
Vec2 rightStick = input.rightStick();
|
||||
|
||||
if (input.down(Gamepad::A)) {
|
||||
// A 键被按住
|
||||
}
|
||||
|
||||
// 振动
|
||||
input.vibrate(0.5f, 0.5f);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 自定义模块
|
||||
|
||||
### 创建模块
|
||||
|
||||
```cpp
|
||||
#include <extra2d/core/module.h>
|
||||
|
||||
class GameModule : public extra2d::Module {
|
||||
public:
|
||||
const char* getName() const override { return "GameModule"; }
|
||||
|
||||
int getPriority() const override { return 1000; }
|
||||
|
||||
void setupModule() override {
|
||||
// 初始化游戏资源
|
||||
E2D_LOG_INFO("Game module initialized");
|
||||
}
|
||||
|
||||
void destroyModule() override {
|
||||
// 清理资源
|
||||
E2D_LOG_INFO("Game module destroyed");
|
||||
}
|
||||
|
||||
void onUpdate(extra2d::UpdateContext& ctx) override {
|
||||
// 更新游戏逻辑
|
||||
float dt = ctx.getDeltaTime();
|
||||
|
||||
// ...
|
||||
|
||||
ctx.next(); // 继续下一个模块
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 注册模块
|
||||
|
||||
```cpp
|
||||
int main() {
|
||||
Application& app = Application::get();
|
||||
|
||||
GameModule gameModule;
|
||||
app.use(gameModule);
|
||||
|
||||
app.init();
|
||||
app.run();
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 完整示例
|
||||
|
||||
下面是一个完整的游戏示例,展示如何创建一个简单的交互式场景:
|
||||
|
||||
```cpp
|
||||
#include <extra2d/extra2d.h>
|
||||
|
||||
using namespace extra2d;
|
||||
|
||||
// 自定义场景
|
||||
class GameScene : public Scene {
|
||||
public:
|
||||
static Ptr<GameScene> create() {
|
||||
return makeShared<GameScene>();
|
||||
}
|
||||
|
||||
void onEnter() override {
|
||||
Scene::onEnter();
|
||||
|
||||
// 设置背景
|
||||
setBackgroundColor(Color(0.1f, 0.1f, 0.15f, 1.0f));
|
||||
|
||||
// 创建玩家
|
||||
player_ = ShapeNode::createFilledRect(
|
||||
Rect(-25, -25, 50, 50),
|
||||
Color(1.0f, 0.4f, 0.4f, 1.0f)
|
||||
);
|
||||
player_->setPos(getWidth() / 2, getHeight() / 2);
|
||||
addChild(player_);
|
||||
|
||||
// 键盘控制
|
||||
addListener(EventType::KeyPressed, [this](Event& e) {
|
||||
auto& key = std::get<KeyEvent>(e.data);
|
||||
|
||||
if (key.keyCode == static_cast<int>(Key::Escape)) {
|
||||
e.handled = true;
|
||||
Application::get().quit();
|
||||
}
|
||||
});
|
||||
|
||||
E2D_LOG_INFO("Game scene entered");
|
||||
}
|
||||
|
||||
void onUpdate(float dt) override {
|
||||
Scene::onUpdate(dt);
|
||||
|
||||
// 移动玩家
|
||||
auto& input = Application::get().input();
|
||||
|
||||
float speed = 200.0f * dt;
|
||||
Vec2 pos = player_->getPosition();
|
||||
|
||||
if (input.down(Key::W) || input.down(Key::Up)) pos.y -= speed;
|
||||
if (input.down(Key::S) || input.down(Key::Down)) pos.y += speed;
|
||||
if (input.down(Key::A) || input.down(Key::Left)) pos.x -= speed;
|
||||
if (input.down(Key::D) || input.down(Key::Right)) pos.x += speed;
|
||||
|
||||
// 边界检测
|
||||
pos.x = std::clamp(pos.x, 25.0f, getWidth() - 25.0f);
|
||||
pos.y = std::clamp(pos.y, 25.0f, getHeight() - 25.0f);
|
||||
|
||||
player_->setPos(pos);
|
||||
|
||||
// 旋转
|
||||
rotation_ += 90.0f * dt;
|
||||
player_->setRotation(rotation_);
|
||||
}
|
||||
|
||||
private:
|
||||
Ptr<ShapeNode> player_;
|
||||
float rotation_ = 0.0f;
|
||||
};
|
||||
|
||||
int main() {
|
||||
// 配置
|
||||
AppConfig config;
|
||||
config.appName = "My Game";
|
||||
config.appVersion = "1.0.0";
|
||||
|
||||
// 初始化
|
||||
Application& app = Application::get();
|
||||
if (!app.init(config)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 创建并进入场景
|
||||
auto scene = GameScene::create();
|
||||
app.enterScene(scene);
|
||||
|
||||
// 运行
|
||||
app.run();
|
||||
|
||||
// 清理
|
||||
app.shutdown();
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 下一步
|
||||
|
||||
- 查看 [模块系统文档](./module_system.md) 了解更多高级功能
|
||||
- 查看 `examples/` 目录中的示例代码
|
||||
- 阅读 [API 参考](./api_reference.md)(待完善)
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 如何设置窗口大小?
|
||||
|
||||
```cpp
|
||||
WindowModule windowModule;
|
||||
WindowConfigData windowConfig;
|
||||
windowConfig.width = 1920;
|
||||
windowConfig.height = 1080;
|
||||
windowConfig.title = "My Game";
|
||||
windowModule.setWindowConfig(windowConfig);
|
||||
app.use(windowModule);
|
||||
```
|
||||
|
||||
### Q: 如何启用垂直同步?
|
||||
|
||||
```cpp
|
||||
RenderModule renderModule;
|
||||
RenderModuleConfig renderConfig;
|
||||
renderConfig.vsync = true;
|
||||
renderModule.setRenderConfig(renderConfig);
|
||||
app.use(renderModule);
|
||||
```
|
||||
|
||||
### Q: 如何处理窗口大小变化?
|
||||
|
||||
```cpp
|
||||
// 在场景中
|
||||
void onEnter() override {
|
||||
Scene::onEnter();
|
||||
|
||||
addListener(EventType::WindowResize, [this](Event& e) {
|
||||
auto& resize = std::get<WindowResizeEvent>(e.data);
|
||||
E2D_LOG_INFO("Window resized: {}x{}", resize.width, resize.height);
|
||||
|
||||
// 更新视口
|
||||
setViewportSize(resize.width, resize.height);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Q: 如何使用计时器?
|
||||
|
||||
```cpp
|
||||
void onEnter() override {
|
||||
Scene::onEnter();
|
||||
|
||||
auto timerService = Application::get().timers();
|
||||
|
||||
// 延迟执行
|
||||
timerService->addTimer(2.0f, []() {
|
||||
E2D_LOG_INFO("2 seconds passed!");
|
||||
});
|
||||
|
||||
// 重复执行
|
||||
timerService->addRepeatingTimer(1.0f, [](int count) {
|
||||
E2D_LOG_INFO("Count: {}", count);
|
||||
return true; // 返回 false 停止
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 获取帮助
|
||||
|
||||
- GitHub Issues: https://github.com/ChestnutYueyue/extra2d/issues
|
||||
- 查看示例代码: `examples/` 目录
|
||||
35
xmake.lua
35
xmake.lua
|
|
@ -145,7 +145,11 @@ target("demo_basic")
|
|||
set_kind("binary")
|
||||
set_default(false)
|
||||
|
||||
-- 强制链接整个静态库(保留静态初始化的模块注册变量)
|
||||
add_ldflags("-Wl,--whole-archive", {force = true})
|
||||
add_deps("extra2d")
|
||||
add_ldflags("-Wl,--no-whole-archive", {force = true})
|
||||
|
||||
add_files("examples/basic/main.cpp")
|
||||
|
||||
-- 平台配置
|
||||
|
|
@ -165,13 +169,42 @@ target("demo_basic")
|
|||
after_build(install_shaders)
|
||||
target_end()
|
||||
|
||||
-- Hello Module 静态库 - 自定义模块示例
|
||||
target("hello_module_lib")
|
||||
set_kind("static")
|
||||
set_default(false)
|
||||
|
||||
add_deps("extra2d")
|
||||
add_files("examples/hello_module/hello_module.cpp")
|
||||
add_includedirs("examples/hello_module", {public = true})
|
||||
|
||||
-- 平台配置
|
||||
local plat = get_config("plat") or os.host()
|
||||
if plat == "mingw" or plat == "windows" then
|
||||
add_packages("glm", "nlohmann_json", "libsdl2")
|
||||
add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi")
|
||||
elseif plat == "linux" then
|
||||
add_packages("glm", "nlohmann_json", "libsdl2")
|
||||
add_syslinks("GL", "dl", "pthread")
|
||||
elseif plat == "macosx" then
|
||||
add_packages("glm", "nlohmann_json", "libsdl2")
|
||||
add_frameworks("OpenGL", "Cocoa", "IOKit", "CoreVideo")
|
||||
end
|
||||
target_end()
|
||||
|
||||
-- Hello Module 示例 - 展示如何创建自定义模块
|
||||
-- 注意:静态链接时,自定义模块需要直接编译到可执行文件中
|
||||
target("demo_hello_module")
|
||||
set_kind("binary")
|
||||
set_default(false)
|
||||
|
||||
-- 强制链接引擎静态库
|
||||
add_ldflags("-Wl,--whole-archive", {force = true})
|
||||
add_deps("extra2d")
|
||||
add_files("examples/hello_module/*.cpp")
|
||||
add_ldflags("-Wl,--no-whole-archive", {force = true})
|
||||
|
||||
-- 直接编译模块源文件(静态链接时的推荐方式)
|
||||
add_files("examples/hello_module/main.cpp", "examples/hello_module/hello_module.cpp")
|
||||
add_includedirs("examples/hello_module")
|
||||
|
||||
-- 平台配置
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ end
|
|||
-- 定义 Extra2D 引擎库目标
|
||||
function define_extra2d_engine()
|
||||
target("extra2d")
|
||||
set_kind("static")
|
||||
set_kind("static") -- 改回静态库
|
||||
|
||||
-- 引擎核心源文件
|
||||
add_files("Extra2D/src/**.cpp")
|
||||
|
|
|
|||
Loading…
Reference in New Issue