Compare commits

...

9 Commits

Author SHA1 Message Date
ChestnutYueyue d3a8c6c979 Merge dev to master: 重构 graphics 模块目录结构并简化配置系统 2026-02-16 23:16:56 +08:00
ChestnutYueyue ea1bbb891d refactor: 移除事件系统、属性绑定和模块配置相关代码
重构模块系统,简化核心架构:
1. 删除EventDispatcher、EventQueue和EventContext相关代码
2. 移除PropertyBinder和模块属性绑定功能
3. 清理冗余的模块配置类(debug_config.h, resource_config.h等)
4. 合并RenderModule配置到模块头文件
5. 移除ServiceRegistry实现
6. 简化Shader系统实现,移除独立缓存和热重载组件
7. 更新文档说明模块配置方式
8. 修复TexturePool的渲染后端依赖问题
2026-02-16 13:38:48 +08:00
ChestnutYueyue 05ef543615 refactor(输入系统): 重构键盘和鼠标事件处理逻辑
将键盘和鼠标事件处理从SDL事件回调移动到独立的update方法
使用SDL_GetKeyboardState和SDL_GetMouseState统一处理输入状态
2026-02-16 09:45:22 +08:00
ChestnutYueyue 8fc3b794d2 refactor(engine): 重构模块系统与平台后端
- 移除PlatformModule和LoggerModule,改为使用E2D_MODULE宏自动注册模块
- 新增ModuleRegistry和ModuleMeta系统实现模块自发现
- 将BackendFactory从PlatformModule移至独立文件
- 添加export.h统一管理导出宏
- 更新README.md添加模块自发现流程图
- 修复SDL2Input初始化状态管理问题
- 清理不再使用的平台配置相关代码
- 示例项目改为静态链接确保模块自动注册
- 添加属性绑定系统支持运行时反射
2026-02-16 09:29:11 +08:00
ChestnutYueyue a78e6f7a05 fix: 将键盘事件检测从keyCode改为scancode
统一使用scancode作为键盘事件的检测标准,提高跨平台兼容性
2026-02-15 20:48:09 +08:00
ChestnutYueyue efc9961d2d refactor(event): 统一事件类型命名并优化输入系统
- 将事件类型命名从过去式改为现在式(如KeyPressed→KeyPress)
- 合并鼠标事件数据结构,使用统一的MouseEvent替代MouseButtonEvent
- 重构输入系统,使用SDL原生枚举值简化键位映射
- 更新相关文档和示例代码以匹配新的命名规范
2026-02-15 20:42:39 +08:00
ChestnutYueyue e0adaa3263 feat(hello_module): 添加ESC键退出功能
在HelloScene中添加键盘事件监听,当按下ESC键时退出应用。同时简化了头文件引用,使用extra2d.h代替多个单独的头文件。
2026-02-15 20:24:32 +08:00
ChestnutYueyue d06c8735bd docs: 添加快速入门指南并更新文档索引
添加详细的快速入门指南文档,涵盖从安装到创建第一个游戏的完整流程
更新 README.md 中的文档索引部分以包含新指南链接
2026-02-15 20:15:59 +08:00
ChestnutYueyue f8a7fab2e7 feat: 重构模块系统并添加事件监听便捷方法
重构整个模块系统,移除旧的模块注册和初始化机制,改为直接继承 Module 基类的方式。新增 Node 类的事件监听便捷方法,简化事件处理流程。

主要变更包括:
1. 移除 module_config.h、module_initializer.h 和 module_registry.h 等旧模块系统文件
2. 新增 core/module.h 作为新模块系统基础
3. 为 Node 类添加 addListener/removeListener 等事件便捷方法
4. 将原有模块(Logger, Config, Platform等)重构为继承 Module 的新实现
5. 更新 Application 类以支持新的模块管理方式
6. 修改 hello_module 示例展示新模块系统用法
2026-02-15 20:13:18 +08:00
45 changed files with 2914 additions and 2442 deletions

86
CLAUDE.md Normal file
View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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();
};
}

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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); \
}

View File

@ -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);
};

View File

@ -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搜索路径

View File

@ -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_; // 纹理缓存

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -85,4 +85,4 @@ private:
} e2d_backend_reg_##name; \
}
} // namespace extra2d
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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();
// ------------------------------------------------------------------------
// 内部方法
// ------------------------------------------------------------------------

View File

@ -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;

View File

@ -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_;

View File

@ -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");
}

View File

@ -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";
}
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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

View File

@ -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);
}
}
}
}

View File

@ -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 ShiftCtrl等
* @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 ShiftCtrl等
* @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;
}

View File

@ -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 truefalse
*/
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 truefalse
*/
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 truefalse
*/
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 truefalse
*/
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 truefalse
*/
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 truefalse
*/
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

View File

@ -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();
}

View File

@ -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)

View File

@ -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")

View File

@ -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")

View File

@ -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")

View File

@ -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
}

View File

@ -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);
}
}

View File

@ -98,6 +98,7 @@ private:
float rightTrigger_ = 0.0f;
float deadzone_ = 0.15f;
bool initialized_ = false;
EventCallback eventCallback_;
};

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

610
docs/quick_start.md Normal file
View File

@ -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/` 目录

View File

@ -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")
-- 平台配置

View File

@ -17,7 +17,7 @@ end
-- 定义 Extra2D 引擎库目标
function define_extra2d_engine()
target("extra2d")
set_kind("static")
set_kind("static") -- 改回静态库
-- 引擎核心源文件
add_files("Extra2D/src/**.cpp")