2026-02-15 10:11:09 +08:00
|
|
|
|
# Extra2D 模块系统
|
|
|
|
|
|
|
|
|
|
|
|
## 概述
|
|
|
|
|
|
|
2026-02-15 12:36:36 +08:00
|
|
|
|
Extra2D 采用模块化架构设计,所有核心功能通过模块系统和服务系统管理。系统提供:
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
|
|
|
|
|
- **统一的生命周期管理**:初始化、关闭、依赖处理
|
2026-02-15 12:36:36 +08:00
|
|
|
|
- **优先级排序**:确保模块/服务按正确顺序初始化
|
2026-02-15 13:32:42 +08:00
|
|
|
|
- **模块化配置**:每个模块独立管理自己的配置
|
2026-02-15 12:36:36 +08:00
|
|
|
|
- **依赖注入**:通过服务定位器解耦模块间依赖
|
2026-02-15 13:32:42 +08:00
|
|
|
|
- **可扩展性**:新增模块无需修改引擎核心代码
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
|
|
|
|
|
## 架构图
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ Application │
|
2026-02-15 12:36:36 +08:00
|
|
|
|
│ (协调模块和服务,通过服务定位器获取依赖) │
|
2026-02-15 10:11:09 +08:00
|
|
|
|
└─────────────────────────────────────────────────────────────┘
|
|
|
|
|
|
│
|
2026-02-15 12:36:36 +08:00
|
|
|
|
┌───────────────┴───────────────┐
|
|
|
|
|
|
▼ ▼
|
|
|
|
|
|
┌─────────────────────────────┐ ┌─────────────────────────────┐
|
|
|
|
|
|
│ ModuleRegistry │ │ ServiceLocator │
|
|
|
|
|
|
│ (模块注册表,管理平台级模块) │ │ (服务定位器,管理运行时服务) │
|
|
|
|
|
|
└─────────────────────────────┘ └─────────────────────────────┘
|
|
|
|
|
|
│ │
|
|
|
|
|
|
┌─────┴─────┐ ┌───────┴───────┐
|
|
|
|
|
|
▼ ▼ ▼ ▼
|
|
|
|
|
|
┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐
|
|
|
|
|
|
│ Config │ │ Window │ │ Scene │ │ Timer │
|
|
|
|
|
|
│ Module │ │ Module │ │ Service │ │ Service │
|
|
|
|
|
|
└───────────┘ └───────────┘ └───────────┘ └───────────┘
|
2026-02-15 10:11:09 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
## 模块化配置系统
|
|
|
|
|
|
|
|
|
|
|
|
### 设计原则
|
|
|
|
|
|
|
|
|
|
|
|
Extra2D 采用**模块化配置系统**,遵循开闭原则:
|
|
|
|
|
|
|
|
|
|
|
|
- **AppConfig** 只包含应用级别配置(appName, appVersion, organization 等)
|
|
|
|
|
|
- **各模块配置** 由模块自己管理,实现 `IModuleConfig` 接口
|
|
|
|
|
|
- **新增模块** 无需修改引擎核心代码
|
|
|
|
|
|
|
|
|
|
|
|
### 配置文件结构
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
Extra2D/include/extra2d/
|
|
|
|
|
|
├── config/
|
|
|
|
|
|
│ ├── app_config.h # 应用级别配置
|
|
|
|
|
|
│ ├── module_config.h # 模块配置接口
|
|
|
|
|
|
│ └── config_manager.h # 配置管理器
|
|
|
|
|
|
├── platform/
|
|
|
|
|
|
│ ├── window_config.h # 窗口模块配置
|
|
|
|
|
|
│ └── input_config.h # 输入模块配置
|
|
|
|
|
|
├── graphics/
|
|
|
|
|
|
│ └── render_config.h # 渲染模块配置
|
|
|
|
|
|
├── audio/
|
|
|
|
|
|
│ └── audio_config.h # 音频模块配置
|
|
|
|
|
|
├── debug/
|
|
|
|
|
|
│ └── debug_config.h # 调试模块配置
|
|
|
|
|
|
└── resource/
|
|
|
|
|
|
└── resource_config.h # 资源模块配置
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### AppConfig 结构
|
|
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
struct AppConfig {
|
|
|
|
|
|
std::string appName = "Extra2D App";
|
|
|
|
|
|
std::string appVersion = "1.0.0";
|
|
|
|
|
|
std::string organization = "";
|
|
|
|
|
|
std::string configFile = "config.json";
|
|
|
|
|
|
PlatformType targetPlatform = PlatformType::Auto;
|
|
|
|
|
|
|
|
|
|
|
|
static AppConfig createDefault();
|
|
|
|
|
|
bool validate() const;
|
|
|
|
|
|
void reset();
|
|
|
|
|
|
void merge(const AppConfig& other);
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 模块配置示例
|
|
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
// window_config.h
|
|
|
|
|
|
struct WindowConfigData {
|
|
|
|
|
|
std::string title = "Extra2D Application";
|
|
|
|
|
|
int width = 1280;
|
|
|
|
|
|
int height = 720;
|
|
|
|
|
|
WindowMode mode = WindowMode::Windowed;
|
|
|
|
|
|
bool vsync = true;
|
|
|
|
|
|
bool resizable = true;
|
|
|
|
|
|
// ...
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// window_module.h
|
|
|
|
|
|
class WindowModuleConfig : public IModuleConfig {
|
|
|
|
|
|
public:
|
|
|
|
|
|
WindowConfigData windowConfig;
|
|
|
|
|
|
|
|
|
|
|
|
ModuleInfo getModuleInfo() const override;
|
|
|
|
|
|
std::string getConfigSectionName() const override { return "window"; }
|
|
|
|
|
|
bool validate() const override;
|
|
|
|
|
|
void applyPlatformConstraints(PlatformType platform) override;
|
|
|
|
|
|
bool loadFromJson(const void* jsonData) override;
|
|
|
|
|
|
bool saveToJson(void* jsonData) const override;
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-02-15 12:36:36 +08:00
|
|
|
|
## 模块 vs 服务
|
|
|
|
|
|
|
|
|
|
|
|
| 特性 | 模块 (Module) | 服务 (Service) |
|
|
|
|
|
|
|-----|--------------|---------------|
|
|
|
|
|
|
| 用途 | 平台级初始化 | 运行时功能 |
|
|
|
|
|
|
| 生命周期 | Application 管理 | ServiceLocator 管理 |
|
2026-02-15 13:32:42 +08:00
|
|
|
|
| 配置管理 | 独立配置文件 | 无配置 |
|
2026-02-15 12:36:36 +08:00
|
|
|
|
| 依赖方式 | 通过 ModuleRegistry | 通过 ServiceLocator |
|
|
|
|
|
|
| 可替换性 | 编译时确定 | 运行时可替换 |
|
2026-02-15 13:32:42 +08:00
|
|
|
|
| 示例 | Window, Render, Input | Scene, Timer, Event, Camera |
|
2026-02-15 12:36:36 +08:00
|
|
|
|
|
2026-02-15 10:11:09 +08:00
|
|
|
|
## 模块优先级
|
|
|
|
|
|
|
|
|
|
|
|
模块按优先级从小到大初始化,关闭时逆序执行:
|
|
|
|
|
|
|
|
|
|
|
|
| 优先级值 | 枚举名称 | 用途 | 模块示例 |
|
|
|
|
|
|
|---------|---------|------|---------|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
| 0 | `Core` | 核心模块,最先初始化 | Config, Platform, Window |
|
|
|
|
|
|
| 50 | `Input` | 输入系统 | Input |
|
2026-02-15 10:11:09 +08:00
|
|
|
|
| 100 | `Graphics` | 图形渲染 | Render |
|
|
|
|
|
|
| 200 | `Audio` | 音频系统 | Audio |
|
2026-02-15 12:36:36 +08:00
|
|
|
|
| 500 | `Resource` | 资源管理 | Resource |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
2026-02-15 12:36:36 +08:00
|
|
|
|
## 模块系统
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
|
|
|
|
|
### IModuleConfig
|
|
|
|
|
|
|
|
|
|
|
|
模块配置接口,定义模块的元数据和配置:
|
|
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
class IModuleConfig {
|
|
|
|
|
|
public:
|
|
|
|
|
|
virtual ~IModuleConfig() = default;
|
|
|
|
|
|
|
|
|
|
|
|
virtual ModuleInfo getModuleInfo() const = 0;
|
|
|
|
|
|
virtual std::string getConfigSectionName() const = 0;
|
|
|
|
|
|
virtual bool validate() const = 0;
|
|
|
|
|
|
virtual void resetToDefaults() = 0;
|
|
|
|
|
|
virtual bool loadFromJson(const void* jsonData) = 0;
|
|
|
|
|
|
virtual bool saveToJson(void* jsonData) const = 0;
|
|
|
|
|
|
virtual void applyPlatformConstraints(PlatformType platform) {}
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### IModuleInitializer
|
|
|
|
|
|
|
|
|
|
|
|
模块初始化器接口,管理模块的生命周期:
|
|
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
class IModuleInitializer {
|
|
|
|
|
|
public:
|
|
|
|
|
|
virtual ~IModuleInitializer() = default;
|
|
|
|
|
|
|
|
|
|
|
|
virtual ModuleId getModuleId() const = 0;
|
|
|
|
|
|
virtual ModulePriority getPriority() const = 0;
|
|
|
|
|
|
virtual std::vector<ModuleId> getDependencies() const = 0;
|
|
|
|
|
|
|
|
|
|
|
|
virtual bool initialize(const IModuleConfig* config) = 0;
|
|
|
|
|
|
virtual void shutdown() = 0;
|
|
|
|
|
|
virtual bool isInitialized() const = 0;
|
|
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### ModuleRegistry
|
|
|
|
|
|
|
|
|
|
|
|
模块注册表,管理所有模块:
|
|
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
class ModuleRegistry {
|
|
|
|
|
|
public:
|
|
|
|
|
|
static ModuleRegistry& instance();
|
|
|
|
|
|
|
|
|
|
|
|
ModuleId registerModule(
|
|
|
|
|
|
UniquePtr<IModuleConfig> config,
|
|
|
|
|
|
ModuleInitializerFactory factory
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
IModuleConfig* getModuleConfig(ModuleId id);
|
|
|
|
|
|
IModuleInitializer* getInitializer(ModuleId id);
|
2026-02-15 13:32:42 +08:00
|
|
|
|
std::vector<ModuleId> getAllModules() const;
|
2026-02-15 10:11:09 +08:00
|
|
|
|
std::vector<ModuleId> getInitializationOrder() const;
|
2026-02-15 12:36:36 +08:00
|
|
|
|
};
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
## 创建新模块
|
2026-02-15 12:36:36 +08:00
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
### 步骤 1:定义配置数据结构
|
2026-02-15 12:36:36 +08:00
|
|
|
|
|
|
|
|
|
|
```cpp
|
2026-02-15 13:32:42 +08:00
|
|
|
|
// my_module_config.h
|
|
|
|
|
|
#pragma once
|
2026-02-15 12:36:36 +08:00
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
#include <string>
|
2026-02-15 12:36:36 +08:00
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
namespace extra2d {
|
2026-02-15 12:36:36 +08:00
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
struct MyModuleConfigData {
|
|
|
|
|
|
int someSetting = 42;
|
|
|
|
|
|
bool enabled = true;
|
|
|
|
|
|
std::string path = "default";
|
2026-02-15 12:36:36 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
}
|
2026-02-15 12:36:36 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
### 步骤 2:定义配置类
|
2026-02-15 12:36:36 +08:00
|
|
|
|
|
|
|
|
|
|
```cpp
|
2026-02-15 13:32:42 +08:00
|
|
|
|
// my_module.h
|
|
|
|
|
|
#pragma once
|
2026-02-15 12:36:36 +08:00
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
#include <extra2d/config/module_config.h>
|
|
|
|
|
|
#include <extra2d/config/module_initializer.h>
|
|
|
|
|
|
#include "my_module_config.h"
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
namespace extra2d {
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
|
|
|
|
|
class MyModuleConfig : public IModuleConfig {
|
|
|
|
|
|
public:
|
2026-02-15 13:32:42 +08:00
|
|
|
|
MyModuleConfigData config;
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
|
|
|
|
|
ModuleInfo getModuleInfo() const override {
|
|
|
|
|
|
ModuleInfo info;
|
2026-02-15 13:32:42 +08:00
|
|
|
|
info.id = 0;
|
2026-02-15 10:11:09 +08:00
|
|
|
|
info.name = "MyModule";
|
|
|
|
|
|
info.version = "1.0.0";
|
2026-02-15 12:36:36 +08:00
|
|
|
|
info.priority = ModulePriority::Graphics;
|
2026-02-15 10:11:09 +08:00
|
|
|
|
info.enabled = true;
|
|
|
|
|
|
return info;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-15 12:36:36 +08:00
|
|
|
|
std::string getConfigSectionName() const override { return "my_module"; }
|
2026-02-15 13:32:42 +08:00
|
|
|
|
bool validate() const override { return config.someSetting > 0; }
|
|
|
|
|
|
|
|
|
|
|
|
void resetToDefaults() override {
|
|
|
|
|
|
config = MyModuleConfigData{};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void applyPlatformConstraints(PlatformType platform) override {
|
|
|
|
|
|
#ifdef __SWITCH__
|
|
|
|
|
|
config.someSetting = 30; // Switch 平台优化
|
|
|
|
|
|
#else
|
|
|
|
|
|
(void)platform;
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-15 10:11:09 +08:00
|
|
|
|
bool loadFromJson(const void* jsonData) override;
|
|
|
|
|
|
bool saveToJson(void* jsonData) const override;
|
|
|
|
|
|
};
|
2026-02-15 13:32:42 +08:00
|
|
|
|
|
|
|
|
|
|
}
|
2026-02-15 10:11:09 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
### 步骤 3:定义初始化器
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
|
|
|
|
|
```cpp
|
2026-02-15 13:32:42 +08:00
|
|
|
|
// my_module.h (续)
|
2026-02-15 10:11:09 +08:00
|
|
|
|
class MyModuleInitializer : public IModuleInitializer {
|
|
|
|
|
|
public:
|
2026-02-15 13:32:42 +08:00
|
|
|
|
MyModuleInitializer();
|
|
|
|
|
|
~MyModuleInitializer() override;
|
|
|
|
|
|
|
2026-02-15 10:11:09 +08:00
|
|
|
|
ModuleId getModuleId() const override { return moduleId_; }
|
2026-02-15 12:36:36 +08:00
|
|
|
|
ModulePriority getPriority() const override { return ModulePriority::Graphics; }
|
2026-02-15 13:32:42 +08:00
|
|
|
|
std::vector<ModuleId> getDependencies() const override;
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
bool initialize(const IModuleConfig* config) override;
|
|
|
|
|
|
void shutdown() override;
|
2026-02-15 10:11:09 +08:00
|
|
|
|
bool isInitialized() const override { return initialized_; }
|
2026-02-15 13:32:42 +08:00
|
|
|
|
|
2026-02-15 10:11:09 +08:00
|
|
|
|
void setModuleId(ModuleId id) { moduleId_ = id; }
|
|
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
ModuleId moduleId_ = INVALID_MODULE_ID;
|
|
|
|
|
|
bool initialized_ = false;
|
2026-02-15 13:32:42 +08:00
|
|
|
|
MyModuleConfigData config_;
|
2026-02-15 10:11:09 +08:00
|
|
|
|
};
|
2026-02-15 13:32:42 +08:00
|
|
|
|
|
|
|
|
|
|
ModuleId get_my_module_id();
|
|
|
|
|
|
void register_my_module();
|
2026-02-15 10:11:09 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
### 步骤 4:实现模块
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
// my_module.cpp
|
2026-02-15 13:32:42 +08:00
|
|
|
|
#include "my_module.h"
|
2026-02-15 10:11:09 +08:00
|
|
|
|
#include <extra2d/config/module_registry.h>
|
2026-02-15 13:32:42 +08:00
|
|
|
|
#include <extra2d/utils/logger.h>
|
|
|
|
|
|
#include <nlohmann/json.hpp>
|
|
|
|
|
|
|
|
|
|
|
|
using json = nlohmann::json;
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
|
|
|
|
|
namespace extra2d {
|
|
|
|
|
|
|
|
|
|
|
|
static ModuleId s_myModuleId = INVALID_MODULE_ID;
|
|
|
|
|
|
|
2026-02-15 12:36:36 +08:00
|
|
|
|
ModuleId get_my_module_id() { return s_myModuleId; }
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
bool MyModuleConfig::loadFromJson(const void* jsonData) {
|
|
|
|
|
|
if (!jsonData) return false;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const json& j = *static_cast<const json*>(jsonData);
|
|
|
|
|
|
if (j.contains("someSetting")) config.someSetting = j["someSetting"].get<int>();
|
|
|
|
|
|
if (j.contains("enabled")) config.enabled = j["enabled"].get<bool>();
|
|
|
|
|
|
if (j.contains("path")) config.path = j["path"].get<std::string>();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
} catch (...) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool MyModuleConfig::saveToJson(void* jsonData) const {
|
|
|
|
|
|
if (!jsonData) return false;
|
|
|
|
|
|
try {
|
|
|
|
|
|
json& j = *static_cast<json*>(jsonData);
|
|
|
|
|
|
j["someSetting"] = config.someSetting;
|
|
|
|
|
|
j["enabled"] = config.enabled;
|
|
|
|
|
|
j["path"] = config.path;
|
|
|
|
|
|
return true;
|
|
|
|
|
|
} catch (...) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
MyModuleInitializer::MyModuleInitializer() : moduleId_(INVALID_MODULE_ID), initialized_(false) {}
|
|
|
|
|
|
|
|
|
|
|
|
MyModuleInitializer::~MyModuleInitializer() { if (initialized_) shutdown(); }
|
|
|
|
|
|
|
|
|
|
|
|
std::vector<ModuleId> MyModuleInitializer::getDependencies() const {
|
|
|
|
|
|
return {}; // 无依赖
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool MyModuleInitializer::initialize(const IModuleConfig* config) {
|
|
|
|
|
|
if (initialized_) return true;
|
|
|
|
|
|
|
|
|
|
|
|
const MyModuleConfig* cfg = dynamic_cast<const MyModuleConfig*>(config);
|
|
|
|
|
|
if (!cfg) {
|
|
|
|
|
|
E2D_LOG_ERROR("Invalid MyModule config");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
config_ = cfg->config;
|
|
|
|
|
|
|
|
|
|
|
|
// 执行初始化逻辑...
|
|
|
|
|
|
|
|
|
|
|
|
initialized_ = true;
|
|
|
|
|
|
E2D_LOG_INFO("MyModule initialized with setting: {}", config_.someSetting);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void MyModuleInitializer::shutdown() {
|
|
|
|
|
|
if (!initialized_) return;
|
|
|
|
|
|
// 执行清理逻辑...
|
|
|
|
|
|
initialized_ = false;
|
|
|
|
|
|
E2D_LOG_INFO("MyModule shutdown");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-15 10:11:09 +08:00
|
|
|
|
void register_my_module() {
|
|
|
|
|
|
if (s_myModuleId != INVALID_MODULE_ID) return;
|
|
|
|
|
|
|
|
|
|
|
|
s_myModuleId = ModuleRegistry::instance().registerModule(
|
|
|
|
|
|
makeUnique<MyModuleConfig>(),
|
|
|
|
|
|
[]() -> UniquePtr<IModuleInitializer> {
|
|
|
|
|
|
auto initializer = makeUnique<MyModuleInitializer>();
|
|
|
|
|
|
initializer->setModuleId(s_myModuleId);
|
|
|
|
|
|
return initializer;
|
|
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
namespace {
|
|
|
|
|
|
struct MyModuleAutoRegister {
|
|
|
|
|
|
MyModuleAutoRegister() { register_my_module(); }
|
|
|
|
|
|
};
|
|
|
|
|
|
static MyModuleAutoRegister s_autoRegister;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
2026-02-15 10:11:09 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-15 12:36:36 +08:00
|
|
|
|
---
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
|
|
|
|
|
## 内置模块
|
|
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
### Config 模块
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
**职责**:管理 ConfigManager 和应用配置
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
|
|
|
|
|
**配置**:
|
|
|
|
|
|
```cpp
|
2026-02-15 13:32:42 +08:00
|
|
|
|
AppConfig config;
|
|
|
|
|
|
config.appName = "My Application";
|
|
|
|
|
|
config.appVersion = "1.0.0";
|
2026-02-15 10:11:09 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
### Platform 模块
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
**职责**:平台检测和平台特定初始化
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
**支持平台**:
|
|
|
|
|
|
- Windows
|
|
|
|
|
|
- Linux
|
|
|
|
|
|
- macOS
|
|
|
|
|
|
- Nintendo Switch
|
|
|
|
|
|
|
|
|
|
|
|
**平台能力查询**:
|
2026-02-15 10:11:09 +08:00
|
|
|
|
```cpp
|
2026-02-15 13:32:42 +08:00
|
|
|
|
auto* platformConfig = createPlatformConfig();
|
|
|
|
|
|
const auto& caps = platformConfig->capabilities();
|
|
|
|
|
|
if (caps.supportsGamepad) {
|
|
|
|
|
|
// 支持手柄
|
|
|
|
|
|
}
|
2026-02-15 10:11:09 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
### Window 模块
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
**职责**:窗口创建和管理
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
**后端**:统一使用 SDL2,支持所有平台
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
**配置**:
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
WindowConfigData config;
|
|
|
|
|
|
config.title = "My App";
|
|
|
|
|
|
config.width = 1280;
|
|
|
|
|
|
config.height = 720;
|
|
|
|
|
|
config.mode = WindowMode::Windowed;
|
|
|
|
|
|
config.vsync = true;
|
|
|
|
|
|
```
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
**平台约束**:
|
|
|
|
|
|
- Switch 平台自动强制全屏模式
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
---
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
### Input 模块
|
|
|
|
|
|
|
|
|
|
|
|
**职责**:输入设备管理(键盘、鼠标、手柄)
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
|
|
|
|
|
**配置**:
|
|
|
|
|
|
```cpp
|
2026-02-15 13:32:42 +08:00
|
|
|
|
InputConfigData config;
|
|
|
|
|
|
config.deadzone = 0.15f;
|
|
|
|
|
|
config.mouseSensitivity = 1.0f;
|
|
|
|
|
|
config.enableVibration = true;
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**使用示例**:
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
IInput* input = app.window().input();
|
|
|
|
|
|
|
|
|
|
|
|
// 键盘
|
|
|
|
|
|
if (input->pressed(Key::Space)) {
|
|
|
|
|
|
// 空格键刚按下
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 鼠标
|
|
|
|
|
|
if (input->down(Mouse::Left)) {
|
|
|
|
|
|
Vec2 pos = input->mouse();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 手柄
|
|
|
|
|
|
if (input->gamepad()) {
|
|
|
|
|
|
Vec2 stick = input->leftStick();
|
|
|
|
|
|
if (input->pressed(Gamepad::A)) {
|
|
|
|
|
|
input->vibrate(0.5f, 0.5f); // 振动反馈
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-15 10:11:09 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### Render 模块
|
|
|
|
|
|
|
|
|
|
|
|
**职责**:渲染器初始化和管理
|
|
|
|
|
|
|
|
|
|
|
|
**配置**:
|
|
|
|
|
|
```cpp
|
2026-02-15 13:32:42 +08:00
|
|
|
|
RenderConfigData config;
|
2026-02-15 10:11:09 +08:00
|
|
|
|
config.backend = BackendType::OpenGL;
|
|
|
|
|
|
config.vsync = true;
|
|
|
|
|
|
config.targetFPS = 60;
|
|
|
|
|
|
config.multisamples = 4;
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-15 12:36:36 +08:00
|
|
|
|
---
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
|
|
|
|
|
## 配置文件格式
|
|
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
配置使用 JSON 格式,每个模块有独立的配置节:
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
2026-02-15 13:32:42 +08:00
|
|
|
|
"app": {
|
|
|
|
|
|
"name": "My Application",
|
|
|
|
|
|
"version": "1.0.0",
|
|
|
|
|
|
"organization": "MyCompany"
|
2026-02-15 10:11:09 +08:00
|
|
|
|
},
|
|
|
|
|
|
"window": {
|
|
|
|
|
|
"title": "My Application",
|
|
|
|
|
|
"width": 1280,
|
|
|
|
|
|
"height": 720,
|
2026-02-15 13:32:42 +08:00
|
|
|
|
"mode": "windowed",
|
2026-02-15 10:11:09 +08:00
|
|
|
|
"vsync": true
|
|
|
|
|
|
},
|
|
|
|
|
|
"render": {
|
|
|
|
|
|
"targetFPS": 60,
|
|
|
|
|
|
"multisamples": 4
|
2026-02-15 13:32:42 +08:00
|
|
|
|
},
|
|
|
|
|
|
"input": {
|
|
|
|
|
|
"deadzone": 0.15,
|
|
|
|
|
|
"mouseSensitivity": 1.0,
|
|
|
|
|
|
"enableVibration": true
|
2026-02-15 10:11:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-15 12:36:36 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
## 服务系统
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
### IService
|
|
|
|
|
|
|
|
|
|
|
|
服务接口基类,所有服务必须实现:
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
2026-02-15 12:36:36 +08:00
|
|
|
|
```cpp
|
2026-02-15 13:32:42 +08:00
|
|
|
|
class IService {
|
|
|
|
|
|
public:
|
|
|
|
|
|
virtual ~IService() = default;
|
|
|
|
|
|
|
|
|
|
|
|
virtual ServiceInfo getServiceInfo() const = 0;
|
|
|
|
|
|
virtual bool initialize() = 0;
|
|
|
|
|
|
virtual void shutdown() = 0;
|
|
|
|
|
|
virtual void update(float deltaTime);
|
|
|
|
|
|
virtual bool isInitialized() const;
|
|
|
|
|
|
|
|
|
|
|
|
ServiceState getState() const;
|
|
|
|
|
|
const std::string& getName() const;
|
2026-02-15 12:36:36 +08:00
|
|
|
|
};
|
2026-02-15 13:32:42 +08:00
|
|
|
|
```
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
### 内置服务
|
|
|
|
|
|
|
|
|
|
|
|
| 服务 | 用途 | 优先级 |
|
|
|
|
|
|
|-----|------|-------|
|
|
|
|
|
|
| SceneService | 场景管理 | 300 |
|
|
|
|
|
|
| TimerService | 计时器 | 200 |
|
|
|
|
|
|
| EventService | 事件分发 | 100 |
|
|
|
|
|
|
| CameraService | 相机系统 | 400 |
|
|
|
|
|
|
|
|
|
|
|
|
### 使用服务
|
|
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
// 获取服务
|
|
|
|
|
|
auto sceneService = Application::get().scenes();
|
|
|
|
|
|
auto timerService = Application::get().timers();
|
|
|
|
|
|
auto eventService = Application::get().events();
|
|
|
|
|
|
|
|
|
|
|
|
// 使用场景服务
|
|
|
|
|
|
sceneService->pushScene(myScene);
|
|
|
|
|
|
|
|
|
|
|
|
// 使用计时器服务
|
|
|
|
|
|
timerService->addTimer(1.0f, []() {
|
|
|
|
|
|
E2D_LOG_INFO("Timer fired!");
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 使用事件服务
|
|
|
|
|
|
eventService->addListener(EventType::KeyPressed, [](Event& e) {
|
|
|
|
|
|
auto& keyEvent = std::get<KeyEvent>(e.data);
|
|
|
|
|
|
E2D_LOG_INFO("Key pressed: {}", keyEvent.keyCode);
|
|
|
|
|
|
});
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 输入事件系统
|
|
|
|
|
|
|
|
|
|
|
|
### 事件类型
|
|
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
enum class EventType {
|
|
|
|
|
|
// 键盘
|
|
|
|
|
|
KeyPressed,
|
|
|
|
|
|
KeyReleased,
|
|
|
|
|
|
KeyRepeat,
|
|
|
|
|
|
|
|
|
|
|
|
// 鼠标
|
|
|
|
|
|
MouseButtonPressed,
|
|
|
|
|
|
MouseButtonReleased,
|
|
|
|
|
|
MouseMoved,
|
|
|
|
|
|
MouseScrolled,
|
|
|
|
|
|
|
|
|
|
|
|
// 手柄
|
|
|
|
|
|
GamepadConnected,
|
|
|
|
|
|
GamepadDisconnected,
|
|
|
|
|
|
GamepadButtonPressed,
|
|
|
|
|
|
GamepadButtonReleased,
|
|
|
|
|
|
|
|
|
|
|
|
// 触摸
|
|
|
|
|
|
TouchBegan,
|
|
|
|
|
|
TouchMoved,
|
|
|
|
|
|
TouchEnded,
|
|
|
|
|
|
|
|
|
|
|
|
// 窗口
|
|
|
|
|
|
WindowResize,
|
|
|
|
|
|
WindowClose,
|
|
|
|
|
|
// ...
|
2026-02-15 12:36:36 +08:00
|
|
|
|
};
|
2026-02-15 10:11:09 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
### 事件监听
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
|
|
|
|
|
```cpp
|
2026-02-15 13:32:42 +08:00
|
|
|
|
auto eventService = Application::get().events();
|
|
|
|
|
|
|
|
|
|
|
|
// 监听键盘事件
|
|
|
|
|
|
eventService->addListener(EventType::KeyPressed, [](Event& e) {
|
|
|
|
|
|
auto& key = std::get<KeyEvent>(e.data);
|
|
|
|
|
|
E2D_LOG_INFO("Key: {}, mods: {}", key.keyCode, key.mods);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 监听鼠标事件
|
|
|
|
|
|
eventService->addListener(EventType::MouseButtonPressed, [](Event& e) {
|
|
|
|
|
|
auto& mouse = std::get<MouseButtonEvent>(e.data);
|
|
|
|
|
|
E2D_LOG_INFO("Mouse button: {} at ({}, {})",
|
|
|
|
|
|
mouse.button, mouse.position.x, mouse.position.y);
|
|
|
|
|
|
});
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 平台支持
|
|
|
|
|
|
|
|
|
|
|
|
### 支持的平台
|
|
|
|
|
|
|
|
|
|
|
|
| 平台 | 窗口后端 | 图形 API | 特殊处理 |
|
|
|
|
|
|
|-----|---------|---------|---------|
|
|
|
|
|
|
| Windows | SDL2 | OpenGL ES 3.2 | - |
|
|
|
|
|
|
| Linux | SDL2 | OpenGL ES 3.2 | - |
|
|
|
|
|
|
| macOS | SDL2 | OpenGL ES 3.2 | - |
|
|
|
|
|
|
| Nintendo Switch | SDL2 | OpenGL ES 3.2 | romfs, 强制全屏 |
|
|
|
|
|
|
|
|
|
|
|
|
### 平台检测
|
|
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
PlatformType platform = PlatformDetector::detect();
|
|
|
|
|
|
const char* name = getPlatformTypeName(platform);
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
switch (platform) {
|
|
|
|
|
|
case PlatformType::Windows: // Windows 处理
|
|
|
|
|
|
case PlatformType::Switch: // Switch 处理
|
|
|
|
|
|
// ...
|
2026-02-15 10:11:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
### 平台能力
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
|
|
|
|
|
```cpp
|
2026-02-15 13:32:42 +08:00
|
|
|
|
auto* config = createPlatformConfig();
|
|
|
|
|
|
const auto& caps = config->capabilities();
|
|
|
|
|
|
|
|
|
|
|
|
if (caps.supportsWindowed) { /* 支持窗口模式 */ }
|
|
|
|
|
|
if (caps.supportsGamepad) { /* 支持手柄 */ }
|
|
|
|
|
|
if (caps.supportsTouch) { /* 支持触摸 */ }
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
2026-02-15 12:36:36 +08:00
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
## 最佳实践
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 模块配置独立化
|
|
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
// 好的做法:模块管理自己的配置
|
|
|
|
|
|
class WindowModuleConfig : public IModuleConfig {
|
|
|
|
|
|
WindowConfigData windowConfig; // 模块内部配置
|
|
|
|
|
|
};
|
2026-02-15 12:36:36 +08:00
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
// 不好的做法:所有配置放在 AppConfig
|
|
|
|
|
|
struct AppConfig {
|
|
|
|
|
|
WindowConfigData window; // 耦合度高
|
|
|
|
|
|
RenderConfigData render;
|
|
|
|
|
|
// ... 新增模块需要修改 AppConfig
|
|
|
|
|
|
};
|
2026-02-15 10:11:09 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-15 13:32:42 +08:00
|
|
|
|
### 2. 使用平台约束
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
|
|
|
|
|
```cpp
|
2026-02-15 13:32:42 +08:00
|
|
|
|
void WindowModuleConfig::applyPlatformConstraints(PlatformType platform) {
|
|
|
|
|
|
#ifdef __SWITCH__
|
|
|
|
|
|
windowConfig.mode = WindowMode::Fullscreen;
|
|
|
|
|
|
windowConfig.resizable = false;
|
|
|
|
|
|
#else
|
|
|
|
|
|
(void)platform;
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3. 模块自动注册
|
|
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
struct MyModuleAutoRegister {
|
|
|
|
|
|
MyModuleAutoRegister() { register_my_module(); }
|
|
|
|
|
|
};
|
|
|
|
|
|
static MyModuleAutoRegister s_autoRegister; // 程序启动时自动注册
|
2026-02-15 10:11:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-15 12:36:36 +08:00
|
|
|
|
---
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
|
|
|
|
|
## 调试
|
|
|
|
|
|
|
|
|
|
|
|
### 查看模块初始化顺序
|
|
|
|
|
|
|
|
|
|
|
|
```cpp
|
|
|
|
|
|
auto order = ModuleRegistry::instance().getInitializationOrder();
|
|
|
|
|
|
for (ModuleId id : order) {
|
|
|
|
|
|
auto* config = ModuleRegistry::instance().getModuleConfig(id);
|
|
|
|
|
|
if (config) {
|
|
|
|
|
|
auto info = config->getModuleInfo();
|
|
|
|
|
|
E2D_LOG_INFO("Module: {} (priority: {})",
|
|
|
|
|
|
info.name, static_cast<int>(info.priority));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-15 12:36:36 +08:00
|
|
|
|
### 查看服务状态
|
2026-02-15 10:11:09 +08:00
|
|
|
|
|
|
|
|
|
|
```cpp
|
2026-02-15 12:36:36 +08:00
|
|
|
|
auto services = ServiceLocator::instance().getAllServices();
|
|
|
|
|
|
for (const auto& service : services) {
|
|
|
|
|
|
auto info = service->getServiceInfo();
|
|
|
|
|
|
E2D_LOG_INFO("Service: {} (state: {})",
|
|
|
|
|
|
info.name, static_cast<int>(info.state));
|
2026-02-15 10:11:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-02-15 12:36:36 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
2026-02-15 10:11:09 +08:00
|
|
|
|
## 示例
|
|
|
|
|
|
|
|
|
|
|
|
完整示例请参考:
|
|
|
|
|
|
- [examples/basic/main.cpp](../../examples/basic/main.cpp) - 基础示例
|
|
|
|
|
|
- [Extra2D/src/platform/window_module.cpp](../../Extra2D/src/platform/window_module.cpp) - Window 模块实现
|
2026-02-15 13:32:42 +08:00
|
|
|
|
- [Extra2D/src/platform/input_module.cpp](../../Extra2D/src/platform/input_module.cpp) - Input 模块实现
|
2026-02-15 10:11:09 +08:00
|
|
|
|
- [Extra2D/src/graphics/render_module.cpp](../../Extra2D/src/graphics/render_module.cpp) - Render 模块实现
|