From 92be7d9d1890baf4051e207e923fcbf9944dff13 Mon Sep 17 00:00:00 2001 From: ChestnutYueyue <952134128@qq.com> Date: Mon, 2 Mar 2026 04:50:28 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=9C=BA=E6=99=AF=E5=9B=BE=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F):=20=E5=AE=9E=E7=8E=B0=E5=AE=8C=E6=95=B4=E7=9A=84?= =?UTF-8?q?=E5=9C=BA=E6=99=AF=E5=9B=BE=E6=A8=A1=E5=9D=97=E5=92=8C=E7=A4=BA?= =?UTF-8?q?=E4=BE=8B=E7=A8=8B=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增场景图系统核心组件: - Director 场景管理 - Scene 场景容器 - Node 节点层级 - Component 组件系统 - Transform 变换(含锚点) - Camera 相机 - SpriteRenderer 精灵渲染 添加场景图示例程序,演示: - 节点层级和变换继承 - 组件系统使用 - 相机设置 - 精灵渲染 同时优化了渲染系统: - 修改渲染命令结构 - 添加视口适配器 - 改进着色器错误处理 - 增强材质系统功能 --- examples/scene_graph_demo/README.md | 96 ++++ examples/scene_graph_demo/game_scene.cpp | 170 +++++++ examples/scene_graph_demo/game_scene.h | 140 ++++++ examples/scene_graph_demo/main.cpp | 79 ++++ examples/scene_graph_demo/xmake.lua | 87 ++++ include/event/events.h | 16 + include/extra2d.h | 11 + include/platform/window_module.h | 10 + include/renderer/material.h | 99 +++- include/renderer/render_types.h | 99 ++-- include/renderer/renderer_module.h | 56 ++- include/renderer/viewport_adapter.h | 139 ++++++ include/scene/component.h | 83 ++++ include/scene/components/camera_component.h | 280 ++++++++++++ include/scene/components/sprite_renderer.h | 121 +++++ .../scene/components/transform_component.h | 167 +++++++ include/scene/director.h | 132 ++++++ include/scene/node.h | 322 +++++++++++++ include/scene/scene.h | 122 +++++ include/scene/scene_module.h | 50 ++ include/types/math/mat4.h | 14 + include/types/math/transform.h | 29 +- shader/default.frag | 29 +- shader/default.vert | 35 +- src/context/context.cpp | 3 + src/platform/window_module.cpp | 10 + src/renderer/material.cpp | 140 +++++- src/renderer/mesh.cpp | 24 + src/renderer/renderer_module.cpp | 151 ++++-- src/renderer/shader.cpp | 21 +- src/renderer/uniform_buffer.cpp | 2 + src/renderer/viewport_adapter.cpp | 431 ++++++++++++++++++ src/scene/component.cpp | 42 ++ src/scene/components/camera_component.cpp | 313 +++++++++++++ src/scene/components/sprite_renderer.cpp | 52 +++ src/scene/components/transform_component.cpp | 163 +++++++ src/scene/director.cpp | 154 +++++++ src/scene/node.cpp | 207 +++++++++ src/scene/scene.cpp | 128 ++++++ src/scene/scene_module.cpp | 56 +++ src/types/math/math_types.cpp | 15 + xmake.lua | 1 + 42 files changed, 4142 insertions(+), 157 deletions(-) create mode 100644 examples/scene_graph_demo/README.md create mode 100644 examples/scene_graph_demo/game_scene.cpp create mode 100644 examples/scene_graph_demo/game_scene.h create mode 100644 examples/scene_graph_demo/main.cpp create mode 100644 examples/scene_graph_demo/xmake.lua create mode 100644 include/renderer/viewport_adapter.h create mode 100644 include/scene/component.h create mode 100644 include/scene/components/camera_component.h create mode 100644 include/scene/components/sprite_renderer.h create mode 100644 include/scene/components/transform_component.h create mode 100644 include/scene/director.h create mode 100644 include/scene/node.h create mode 100644 include/scene/scene.h create mode 100644 include/scene/scene_module.h create mode 100644 include/types/math/mat4.h create mode 100644 src/renderer/viewport_adapter.cpp create mode 100644 src/scene/component.cpp create mode 100644 src/scene/components/camera_component.cpp create mode 100644 src/scene/components/sprite_renderer.cpp create mode 100644 src/scene/components/transform_component.cpp create mode 100644 src/scene/director.cpp create mode 100644 src/scene/node.cpp create mode 100644 src/scene/scene.cpp create mode 100644 src/scene/scene_module.cpp create mode 100644 src/types/math/math_types.cpp diff --git a/examples/scene_graph_demo/README.md b/examples/scene_graph_demo/README.md new file mode 100644 index 0000000..f2af0e4 --- /dev/null +++ b/examples/scene_graph_demo/README.md @@ -0,0 +1,96 @@ +# 场景图系统示例 (Scene Graph Demo) + +这个示例演示了 Extra2D 引擎场景图模块的核心功能。 + +## 功能演示 + +### 1. Director 场景管理 +- 通过 `SceneModule` 获取 `Director` +- 使用 `runScene()` 启动场景 + +### 2. Scene 场景容器 +- 继承 `Scene` 类创建自定义场景 +- 实现 `onEnter()` 和 `onExit()` 生命周期方法 +- 在 `update()` 中处理场景级逻辑 + +### 3. Node 节点层级 +- 创建自定义节点类(`PlayerNode`, `RotatingDecoration`) +- 使用 `addChild()` 构建节点树 +- 节点变换自动继承父节点 + +### 4. Component 组件系统 +- `TransformComponent`: 位置、旋转、缩放、锚点 +- `SpriteRenderer`: 2D 精灵渲染 +- `CameraComponent`: 相机投影和视图 + +### 5. Transform 变换(含锚点) +- 归一化锚点设置 `[0,1]` +- 锚点影响旋转中心和位置参考点 +- 世界变换自动计算 + +## 运行效果 + +- 蓝色方块(玩家)沿圆周运动 +- 8个彩色小方块围绕中心旋转 +- 每个小方块还有自转动画 + +## 构建和运行 + +```bash +# 配置(启用示例) +xmake f -p mingw --examples=true + +# 构建 +xmake + +# 运行 +./build/examples/scene_graph_demo/scene_graph_demo.exe +``` + +## 代码结构 + +``` +scene_graph_demo/ +├── main.cpp # 程序入口 +├── game_scene.h # 场景和节点类定义 +├── game_scene.cpp # 场景和节点实现 +└── xmake.lua # 构建配置 +``` + +## 关键 API + +### 创建场景 +```cpp +class GameScene : public Scene { +public: + void onEnter() override; + void update(float dt) override; +}; + +auto scene = makePtr(); +director->runScene(scene); +``` + +### 创建节点 +```cpp +auto node = makePtr(); +node->setPosition(100, 200); +node->setAnchor(0.5f, 0.5f); // 中心锚点 +node->setSize(64, 64); +scene->addChild(node); +``` + +### 添加组件 +```cpp +auto sprite = makePtr(); +sprite->setColor(Color::Red); +node->addComponent(sprite); +``` + +### 设置相机 +```cpp +auto camera = makePtr(); +camera->setOrtho(0, 1280, 0, 720, -1, 1); +cameraNode->addComponent(camera); +scene->setMainCamera(camera); +``` diff --git a/examples/scene_graph_demo/game_scene.cpp b/examples/scene_graph_demo/game_scene.cpp new file mode 100644 index 0000000..8889581 --- /dev/null +++ b/examples/scene_graph_demo/game_scene.cpp @@ -0,0 +1,170 @@ +#include "game_scene.h" +#include + +// ======================================== +// PlayerNode 实现 +// ======================================== + +PlayerNode::PlayerNode() { + setName("Player"); + setTag(1); + + // 设置玩家尺寸 + setSize(64.0f, 64.0f); + + // 设置锚点为中心点 + setAnchor(0.5f, 0.5f); + + // 添加精灵渲染组件 + auto sprite = makePtr(); + sprite->setColor(Color::Blue); + addComponent(sprite); +} + +void PlayerNode::onUpdate(float dt) { + time_ += dt; + + // 简单的圆周运动 + float radius = 200.0f; + float x = 640.0f + radius * std::cos(time_ * 0.5f); + float y = 360.0f + radius * std::sin(time_ * 0.5f); + + setPosition(x, y); + + // 根据移动方向旋转 + float angle = time_ * 0.5f * 57.2958f + 90.0f; + setRotation(angle); +} + +// ======================================== +// RotatingDecoration 实现 +// ======================================== + +RotatingDecoration::RotatingDecoration() { + setName("Decoration"); + + // 设置尺寸 + setSize(32.0f, 32.0f); + + // 设置锚点为中心 + setAnchor(0.5f, 0.5f); + + // 添加精灵渲染组件 + auto sprite = makePtr(); + sprite->setColor(Color::Yellow); + addComponent(sprite); +} + +void RotatingDecoration::onUpdate(float dt) { + // 自转 + setRotation(getRotation() + rotationSpeed_ * dt); +} + +// ======================================== +// GameScene 实现 +// ======================================== + +GameScene::GameScene() { + // 场景初始化 +} + +void GameScene::onEnter() { + Scene::onEnter(); + + // 创建相机 + createCamera(); + + // 创建玩家 + createPlayer(); + + // 创建装饰物 + createDecorations(); +} + +void GameScene::onExit() { + // 清理资源 + player_.reset(); + decorations_.clear(); + + Scene::onExit(); +} + +void GameScene::update(float dt) { + Scene::update(dt); + + sceneTime_ += dt; + + // 更新玩家 + if (player_) { + player_->onUpdate(dt); + } + + // 更新装饰物 + for (auto& decoration : decorations_) { + decoration->onUpdate(dt); + } +} + +void GameScene::createPlayer() { + player_ = makePtr(); + player_->setPosition(640.0f, 360.0f); + addChild(player_); +} + +void GameScene::createDecorations() { + // 创建围绕玩家旋转的装饰物 + int count = 8; + float radius = 150.0f; + + for (int i = 0; i < count; ++i) { + auto decoration = makePtr(); + + // 计算位置(圆形分布) + float angle = (2.0f * 3.14159f * i) / count; + float x = 640.0f + radius * std::cos(angle); + float y = 360.0f + radius * std::sin(angle); + + decoration->setPosition(x, y); + decoration->setRotationSpeed(45.0f + i * 10.0f); + + // 设置不同颜色 + auto sprite = decoration->getComponent(); + if (sprite) { + float hue = static_cast(i) / count; + // 简单的HSV到RGB转换 + float r = std::abs(hue * 6.0f - 3.0f) - 1.0f; + float g = 2.0f - std::abs(hue * 6.0f - 2.0f); + float b = 2.0f - std::abs(hue * 6.0f - 4.0f); + sprite->setColor(Color( + std::max(0.0f, std::min(1.0f, r)), + std::max(0.0f, std::min(1.0f, g)), + std::max(0.0f, std::min(1.0f, b)), + 1.0f + )); + } + + decorations_.push_back(decoration); + addChild(decoration); + } +} + +void GameScene::createCamera() { + // 创建相机节点 + auto cameraNode = makePtr(); + cameraNode->setName("MainCamera"); + // 相机位置在(0, 0),这样世界坐标直接映射到屏幕 + cameraNode->setPosition(0.0f, 0.0f); + + // 添加相机组件 + auto camera = makePtr(); + // 使用标准的2D投影:左上角为(0, 0),右下角为(1280, 720) + // Y轴向下:bottom=720, top=0 + camera->setOrtho(0.0f, 1280.0f, 720.0f, 0.0f, -1.0f, 1.0f); + cameraNode->addComponent(camera); + + // 设置为主相机 + setMainCamera(camera); + + // 添加相机节点到场景 + addChild(cameraNode); +} diff --git a/examples/scene_graph_demo/game_scene.h b/examples/scene_graph_demo/game_scene.h new file mode 100644 index 0000000..bd8e5d2 --- /dev/null +++ b/examples/scene_graph_demo/game_scene.h @@ -0,0 +1,140 @@ +#pragma once + +#include + +using namespace extra2d; + +/** + * @brief 玩家节点类 + * + * 演示如何创建自定义节点并添加组件 + */ +class PlayerNode : public Node { +public: + /** + * @brief 构造函数 + */ + PlayerNode(); + + /** + * @brief 析构函数 + */ + ~PlayerNode() override = default; + + /** + * @brief 每帧更新 + * @param dt 帧间隔时间 + */ + void onUpdate(float dt); + + /** + * @brief 设置移动速度 + * @param speed 速度 + */ + void setSpeed(float speed) { speed_ = speed; } + + /** + * @brief 获取移动速度 + * @return 速度 + */ + float getSpeed() const { return speed_; } + +private: + float speed_ = 200.0f; + float time_ = 0.0f; +}; + +/** + * @brief 旋转装饰节点 + * + * 演示节点层级关系和变换继承 + */ +class RotatingDecoration : public Node { +public: + /** + * @brief 构造函数 + */ + RotatingDecoration(); + + /** + * @brief 析构函数 + */ + ~RotatingDecoration() override = default; + + /** + * @brief 每帧更新 + * @param dt 帧间隔时间 + */ + void onUpdate(float dt); + + /** + * @brief 设置旋转速度 + * @param speed 旋转速度(度/秒) + */ + void setRotationSpeed(float speed) { rotationSpeed_ = speed; } + +private: + float rotationSpeed_ = 90.0f; +}; + +/** + * @brief 游戏场景类 + * + * 演示场景图系统的核心功能: + * - 节点层级管理 + * - 组件系统 + * - 相机设置 + * - 场景生命周期 + */ +class GameScene : public Scene { +public: + /** + * @brief 构造函数 + */ + GameScene(); + + /** + * @brief 析构函数 + */ + ~GameScene() override = default; + + /** + * @brief 场景进入时调用 + * + * 在这里创建所有游戏对象 + */ + void onEnter() override; + + /** + * @brief 场景退出时调用 + * + * 在这里清理资源 + */ + void onExit() override; + + /** + * @brief 每帧更新 + * @param dt 帧间隔时间 + */ + void update(float dt) override; + +private: + /** + * @brief 创建玩家 + */ + void createPlayer(); + + /** + * @brief 创建装饰物 + */ + void createDecorations(); + + /** + * @brief 创建相机 + */ + void createCamera(); + + Ptr player_; + std::vector> decorations_; + float sceneTime_ = 0.0f; +}; diff --git a/examples/scene_graph_demo/main.cpp b/examples/scene_graph_demo/main.cpp new file mode 100644 index 0000000..4d6cea4 --- /dev/null +++ b/examples/scene_graph_demo/main.cpp @@ -0,0 +1,79 @@ +/** + * @file main.cpp + * @brief 场景图系统示例程序 + * + * 演示 Extra2D 场景图模块的核心功能: + * - Director 场景管理 + * - Scene 场景容器 + * - Node 节点层级 + * - Component 组件系统 + * - Transform 变换(含锚点) + * - Camera 相机 + * - SpriteRenderer 精灵渲染 + */ + +#include +#include "game_scene.h" +#include + +using namespace extra2d; + +/** + * @brief 程序入口 + */ +int main(int argc, char **argv) { + // ======================================== + // 1. 创建应用 + // ======================================== + auto app = Application::create(); + + AppConfig config; + config.title = "Scene Graph Demo - Extra2D"; + config.width = 1280; + config.height = 720; + + if (!app->init(config)) { + printf("Failed to initialize application!\n"); + return -1; + } + + printf("Application initialized successfully\n"); + printf("Window size: %dx%d\n", app->getWindowWidth(), app->getWindowHeight()); + + // ======================================== + // 2. 获取场景模块和导演 + // ======================================== + SceneModule *sceneModule = app->getModule(); + if (!sceneModule) { + printf("Failed to get SceneModule!\n"); + return -1; + } + + Director *director = sceneModule->getDirector(); + if (!director) { + printf("Failed to get Director!\n"); + return -1; + } + + printf("Scene module and director ready\n"); + + // ======================================== + // 3. 创建并运行游戏场景 + // ======================================== + auto gameScene = makePtr(); + director->runScene(gameScene); + + printf("Game scene started\n"); + + // ======================================== + // 4. 运行应用主循环 + // ======================================== + app->run(); + + // ======================================== + // 5. 清理(自动进行) + // ======================================== + printf("Application shutting down...\n"); + + return 0; +} diff --git a/examples/scene_graph_demo/xmake.lua b/examples/scene_graph_demo/xmake.lua new file mode 100644 index 0000000..64f730c --- /dev/null +++ b/examples/scene_graph_demo/xmake.lua @@ -0,0 +1,87 @@ +-- ============================================== +-- 场景图示例 - Xmake 构建脚本 +-- 演示 Extra2D 场景图系统的核心功能 +-- 支持平台: MinGW (Windows), Nintendo Switch +-- ============================================== + +-- 获取当前脚本所在目录(示例根目录) +local example_dir = os.scriptdir() + +-- 可执行文件目标 +target("scene_graph_demo") + set_kind("binary") + add_files("main.cpp", "game_scene.cpp") + add_includedirs("../../include", ".") + add_deps("extra2d") + + -- 使用与主项目相同的平台配置 + if is_plat("switch") then + set_targetdir("../../build/examples/scene_graph_demo") + + -- 构建后生成 NRO 文件 + after_build(function (target) + local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro" + local elf_file = target:targetfile() + local output_dir = path.directory(elf_file) + local nacp_file = path.join(output_dir, "scene_graph_demo.nacp") + local nro_file = path.join(output_dir, "scene_graph_demo.nro") + local nacptool = path.join(devkitPro, "tools/bin/nacptool.exe") + local elf2nro = path.join(devkitPro, "tools/bin/elf2nro.exe") + + if os.isfile(nacptool) and os.isfile(elf2nro) then + os.vrunv(nacptool, {"--create", "Scene Graph Demo", "Extra2D Team", "1.0.0", nacp_file}) + local romfs = path.join(example_dir, "romfs") + if os.isdir(romfs) then + os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file, "--romfsdir=" .. romfs}) + else + os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file}) + end + print("Generated NRO: " .. nro_file) + end + end) + + -- 打包时将 NRO 文件复制到 package 目录 + after_package(function (target) + local nro_file = path.join(target:targetdir(), "scene_graph_demo.nro") + local package_dir = target:packagedir() + if os.isfile(nro_file) and package_dir then + os.cp(nro_file, package_dir) + print("Copied NRO to package: " .. package_dir) + end + end) + + elseif is_plat("mingw") then + set_targetdir("../../build/examples/scene_graph_demo") + + -- 复制资源到输出目录 + after_build(function (target) + local target_dir = path.directory(target:targetfile()) + + -- 复制 romfs 资源 + local romfs = path.join(example_dir, "romfs") + if os.isdir(romfs) then + local assets_dir = path.join(target_dir, "assets") + + -- 创建 assets 目录 + if not os.isdir(assets_dir) then + os.mkdir(assets_dir) + end + + -- 复制所有资源文件(包括子目录) + os.cp(path.join(romfs, "assets/**"), assets_dir) + print("Copied assets from " .. romfs .. " to " .. assets_dir) + end + + -- 复制着色器文件 + local shader_source = path.join(example_dir, "../../shader") + local shader_target = path.join(target_dir, "shader") + if os.isdir(shader_source) then + if not os.isdir(shader_target) then + os.mkdir(shader_target) + end + os.cp(path.join(shader_source, "*"), shader_target) + print("Copied shaders from " .. shader_source .. " to " .. shader_target) + end + end) + end +target_end() diff --git a/include/event/events.h b/include/event/events.h index a0beecf..83d864e 100644 --- a/include/event/events.h +++ b/include/event/events.h @@ -3,6 +3,7 @@ #include #include #include +#include namespace extra2d::events { @@ -243,6 +244,14 @@ DECLARE_EVENT_0(OnRenderBegin, Engine) */ DECLARE_EVENT_1(OnRenderSubmit, Engine, extra2d::RenderCommand) +/** + * @brief 渲染设置相机事件 + * + * 场景图模块通过此事件设置相机矩阵 + * @param viewProjection 视图投影矩阵 + */ +DECLARE_EVENT_1(OnRenderSetCamera, Engine, extra2d::Mat4) + /** * @brief 渲染结束事件 * @@ -250,4 +259,11 @@ DECLARE_EVENT_1(OnRenderSubmit, Engine, extra2d::RenderCommand) */ DECLARE_EVENT_0(OnRenderEnd, Engine) +/** + * @brief 渲染呈现事件 + * + * 在渲染完成并准备好呈现时触发,窗口模块应调用 swapBuffers + */ +DECLARE_EVENT_0(OnRenderPresent, Engine) + } // namespace extra2d::events diff --git a/include/extra2d.h b/include/extra2d.h index 559d463..f49fb43 100644 --- a/include/extra2d.h +++ b/include/extra2d.h @@ -16,6 +16,7 @@ #include #include #include +#include // Context (核心上下文) #include @@ -49,6 +50,16 @@ #include #include +// Scene Graph System +#include +#include +#include +#include +#include +#include +#include +#include + // Application #include diff --git a/include/platform/window_module.h b/include/platform/window_module.h index b062932..d155b90 100644 --- a/include/platform/window_module.h +++ b/include/platform/window_module.h @@ -154,6 +154,13 @@ private: */ void onModuleConfig(const AppConfig &config); + /** + * @brief 监听渲染呈现事件 + * + * 当渲染完成时调用 swapBuffers 呈现渲染结果 + */ + void onRenderPresent(); + SDL_Window *window_ = nullptr; SDL_GLContext glContext_ = nullptr; bool shouldClose_ = false; @@ -163,6 +170,9 @@ private: // 模块配置事件监听器 std::unique_ptr::Listener> configListener_; + + // 渲染呈现事件监听器 + std::unique_ptr renderPresentListener_; }; } // namespace extra2d diff --git a/include/renderer/material.h b/include/renderer/material.h index 53ad2f4..9930f2a 100644 --- a/include/renderer/material.h +++ b/include/renderer/material.h @@ -15,6 +15,7 @@ namespace extra2d { // 前向声明 class Material; +class Texture; /** * @brief 材质参数信息 @@ -25,6 +26,15 @@ struct MaterialParamInfo { uint32_t size; }; +/** + * @brief 纹理槽位信息 + */ +struct TextureSlot { + TextureHandle handle; + uint32_t slot; + std::string uniformName; +}; + /** * @brief 材质布局类 * @@ -77,8 +87,36 @@ private: /** * @brief 材质类 * - * 管理着色器和材质参数 - * 支持通过 UBO 高效上传材质数据到 GPU + * 作为着色器和纹理的中间层,管理: + * - 着色器程序 + * - 材质参数(通过 UBO 上传) + * - 纹理绑定 + * + * 使用示例: + * @code + * // 创建自定义着色器 + * auto shader = makePtr(); + * shader->loadFromFile("custom.vert", "custom.frag"); + * + * // 创建材质布局 + * auto layout = makePtr(); + * layout->addParam("uTintColor", MaterialParamType::Color); + * layout->addParam("uOpacity", MaterialParamType::Float); + * layout->finalize(); + * + * // 创建材质 + * auto material = makePtr(); + * material->setShader(shader); + * material->setLayout(layout); + * material->setColor("uTintColor", Color::White); + * material->setFloat("uOpacity", 1.0f); + * + * // 设置纹理 + * material->setTexture("uTexture", textureHandle, 0); + * + * // 注册到渲染器 + * MaterialHandle handle = renderer->registerMaterial(material); + * @endcode */ class Material : public RefCounted { public: @@ -87,6 +125,10 @@ public: */ Material(); + // ======================================== + // 着色器和布局设置 + // ======================================== + /** * @brief 设置材质布局 * @param layout 材质布局 @@ -99,6 +141,16 @@ public: */ void setShader(Ptr shader); + /** + * @brief 获取着色器 + * @return 着色器 + */ + Ptr getShader() const { return shader_; } + + // ======================================== + // 参数设置 + // ======================================== + /** * @brief 设置 float 参数 * @param name 参数名称 @@ -134,27 +186,32 @@ public: */ void setMat4(const std::string& name, const float* value); + // ======================================== + // 纹理管理 + // ======================================== + /** * @brief 设置纹理 - * @param name 参数名称 + * @param uniformName 着色器中的采样器 uniform 名称 * @param texture 纹理句柄 - * @param slot 纹理槽位 + * @param slot 纹理槽位(0-15) */ - void setTexture(const std::string& name, TextureHandle texture, uint32_t slot); + void setTexture(const std::string& uniformName, TextureHandle texture, uint32_t slot); /** - * @brief 应用材质 - * - * 绑定着色器、上传 UBO 数据、绑定纹理 - * @param uboManager UBO 管理器 + * @brief 获取所有纹理槽位 + * @return 纹理槽位列表 */ - void apply(UniformBufferManager& uboManager); + const std::vector& getTextures() const { return textures_; } /** - * @brief 获取着色器 - * @return 着色器 + * @brief 清除所有纹理 */ - Ptr getShader() const { return shader_; } + void clearTextures(); + + // ======================================== + // 数据访问 + // ======================================== /** * @brief 获取材质数据指针 @@ -167,12 +224,24 @@ public: * @return 数据大小 */ uint32_t getDataSize() const { return static_cast(data_.size()); } + + // ======================================== + // 应用材质(渲染时调用) + // ======================================== + + /** + * @brief 应用材质 + * + * 绑定着色器、上传 UBO 数据 + * @param uboManager UBO 管理器 + */ + void apply(UniformBufferManager& uboManager); private: Ptr layout_; // 材质布局 Ptr shader_; // 着色器 - std::vector data_; // 材质数据 - std::vector> textures_; // 纹理列表 + std::vector data_; // 材质数据(UBO 缓冲) + std::vector textures_; // 纹理槽位列表 }; } // namespace extra2d diff --git a/include/renderer/render_types.h b/include/renderer/render_types.h index bd816ef..b41621e 100644 --- a/include/renderer/render_types.h +++ b/include/renderer/render_types.h @@ -93,42 +93,50 @@ inline uint32_t getMaterialParamSize(MaterialParamType type) { } /** - * @brief 渲染命令结构(32字节对齐) + * @brief 渲染命令结构 * * 使用值类型设计,避免智能指针开销 * 通过资源句柄引用实际资源 */ -struct alignas(32) RenderCommand { +struct RenderCommand { RenderCommandType type; // 命令类型 uint32_t sortKey; // 排序键(材质ID + 层) + // 绘制网格命令数据 + struct DrawMeshData { + MeshHandle mesh; // 网格句柄 + MaterialHandle material; // 材质句柄 + Vec2 pos; // 位置 + Vec2 scale; // 缩放 + float rot; // 旋转角度 + Color color; // 顶点颜色 + }; + + // 实例化绘制命令数据 + struct DrawInstancedData { + MeshHandle mesh; // 网格句柄 + MaterialHandle material; // 材质句柄 + uint32_t instanceCount; // 实例数量 + uint32_t instanceOffset; // 实例数据偏移 + }; + + // 设置视口命令数据 + struct ViewportData { + int32_t x, y; // 视口位置 + int32_t width, height; // 视口大小 + }; + + // 清除缓冲区命令数据 + struct ClearData { + Color color; // 清除颜色 + uint32_t flags; // 清除标志(颜色/深度/模板) + }; + union { - // 绘制网格命令 - struct { - MeshHandle mesh; // 网格句柄 - MaterialHandle material; // 材质句柄 - Transform transform; // 世界变换 - } drawMesh; - - // 实例化绘制命令 - struct { - MeshHandle mesh; // 网格句柄 - MaterialHandle material; // 材质句柄 - uint32_t instanceCount; // 实例数量 - uint32_t instanceOffset; // 实例数据偏移 - } drawInstanced; - - // 设置视口命令 - struct { - int32_t x, y; // 视口位置 - int32_t width, height; // 视口大小 - } viewport; - - // 清除缓冲区命令 - struct { - Color color; // 清除颜色 - uint32_t flags; // 清除标志(颜色/深度/模板) - } clear; + DrawMeshData drawMesh; + DrawInstancedData drawInstanced; + ViewportData viewport; + ClearData clear; }; /** @@ -137,7 +145,40 @@ struct alignas(32) RenderCommand { RenderCommand() : type(RenderCommandType::DrawMesh), sortKey(0) { drawMesh.mesh = INVALID_MESH_HANDLE; drawMesh.material = INVALID_MATERIAL_HANDLE; - drawMesh.transform = Transform(); + drawMesh.pos = Vec2(0.0f, 0.0f); + drawMesh.scale = Vec2(1.0f, 1.0f); + drawMesh.rot = 0.0f; + drawMesh.color = Color::White; + } + + /** + * @brief 从 Transform 设置绘制数据 + */ + void setTransform(const Transform& t) { + drawMesh.pos = t.pos; + drawMesh.scale = t.scale; + drawMesh.rot = t.rot; + } + + /** + * @brief 设置顶点颜色 + */ + void setColor(const Color& c) { + drawMesh.color = c; + } + + /** + * @brief 获取 Transform + */ + Transform getTransform() const { + return Transform(drawMesh.pos, drawMesh.scale, drawMesh.rot); + } + + /** + * @brief 获取顶点颜色 + */ + Color getColor() const { + return drawMesh.color; } }; diff --git a/include/renderer/renderer_module.h b/include/renderer/renderer_module.h index b2a6d83..5400a23 100644 --- a/include/renderer/renderer_module.h +++ b/include/renderer/renderer_module.h @@ -11,6 +11,7 @@ #include #include #include +#include #include namespace extra2d { @@ -169,10 +170,23 @@ public: /** * @brief 清除缓冲区 * @param color 清除颜色 - * @param flags 清除标志(组合使用 CLEAR_COLOR_FLAG, CLEAR_DEPTH_FLAG, CLEAR_STENCIL_FLAG) + * @param flags 清除标志(组合使用 CLEAR_COLOR_FLAG, CLEAR_DEPTH_FLAG, + * CLEAR_STENCIL_FLAG) */ void clear(const Color &color, uint32 flags = CLEAR_COLOR_FLAG); + /** + * @brief 获取视口适配器 + * @return 视口适配器引用 + */ + ViewportAdapter &getViewportAdapter() { return viewportAdapter_; } + + /** + * @brief 获取视口适配器(const版本) + * @return 视口适配器const引用 + */ + const ViewportAdapter &getViewportAdapter() const { return viewportAdapter_; } + private: //=========================================================================== // 事件处理器 @@ -191,6 +205,12 @@ private: */ void onRenderSubmit(const RenderCommand &cmd); + /** + * @brief 渲染设置相机事件处理 + * @param viewProj 视图投影矩阵 + */ + void onRenderSetCamera(const Mat4 &viewProj); + /** * @brief 渲染结束事件处理 * @@ -341,16 +361,17 @@ private: } }; - HandlePool materialPool_; // 材质资源池 - HandlePool meshPool_; // 网格资源池 - HandlePool texturePool_; // 纹理资源池 + HandlePool materialPool_; // 材质资源池 + HandlePool meshPool_; // 网格资源池 + HandlePool texturePool_; // 纹理资源池 //=========================================================================== // 命令缓冲区 //=========================================================================== - std::array commandBuffer_; // 预分配命令缓冲区 - uint32 commandCount_ = 0; // 当前命令数量 + std::array + commandBuffer_; // 预分配命令缓冲区 + uint32 commandCount_ = 0; // 当前命令数量 //=========================================================================== // UBO 管理器 @@ -372,6 +393,7 @@ private: events::OnRenderBegin::Listener onRenderBeginListener_; events::OnRenderSubmit::Listener onRenderSubmitListener_; + events::OnRenderSetCamera::Listener onRenderSetCameraListener_; events::OnRenderEnd::Listener onRenderEndListener_; events::OnResize::Listener onResizeListener_; events::OnShow::Listener onShowListener_; @@ -380,17 +402,17 @@ private: // 状态标志 //=========================================================================== - bool glInitialized_ = false; // GL 是否已初始化 + bool glInitialized_ = false; // GL 是否已初始化 //=========================================================================== // 渲染统计 //=========================================================================== struct Stats { - uint32 commandsSubmitted = 0; // 提交的命令数 - uint32 commandsExecuted = 0; // 执行的命令数 - uint32 drawCalls = 0; // 绘制调用次数 - uint32 batches = 0; // 批次数 + uint32 commandsSubmitted = 0; // 提交的命令数 + uint32 commandsExecuted = 0; // 执行的命令数 + uint32 drawCalls = 0; // 绘制调用次数 + uint32 batches = 0; // 批次数 } stats_; //=========================================================================== @@ -399,6 +421,18 @@ private: int32 viewportX_ = 0, viewportY_ = 0; int32 viewportWidth_ = 0, viewportHeight_ = 0; + + //=========================================================================== + // 视口适配器 + //=========================================================================== + + ViewportAdapter viewportAdapter_; // 视口适配器 + + //=========================================================================== + // 相机矩阵 + //=========================================================================== + + Mat4 viewProjectionMatrix_; // 当前视图投影矩阵 }; } // namespace extra2d diff --git a/include/renderer/viewport_adapter.h b/include/renderer/viewport_adapter.h new file mode 100644 index 0000000..eaf4633 --- /dev/null +++ b/include/renderer/viewport_adapter.h @@ -0,0 +1,139 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 视口适配模式枚举 + */ +enum class ViewportMode { + AspectRatio, // 保持宽高比,可能有黑边 + Stretch, // 拉伸以填满屏幕 + Center, // 居中显示 + Custom // 自定义 +}; + +/** + * @brief 黑边位置枚举 + */ +enum class LetterboxPosition { + Center, + LeftTop, + RightTop, + LeftBottom, + RightBottom +}; + +/** + * @brief 视口配置结构体 + */ +struct ViewportConfig { + float logicWidth = 1280.0f; // 逻辑宽度 + float logicHeight = 720.0f; // 逻辑高度 + ViewportMode mode = ViewportMode::AspectRatio; + LetterboxPosition letterboxPosition = LetterboxPosition::Center; + Color letterboxColor = Color::Black; + bool autoScaleInCenterMode = true; + float customScale = 1.0f; + Vec2 customOffset = Vec2::Zero; + Rect customViewport = Rect::Zero; +}; + +/** + * @brief 视口计算结果结构体 + */ +struct ViewportResult { + Rect viewport; // 视口矩形 + float scaleX = 1.0f; // X方向缩放 + float scaleY = 1.0f; // Y方向缩放 + float uniformScale = 1.0f; // 统一缩放 + Vec2 offset; // 视口偏移 + bool hasLetterbox = false; // 是否有黑边 + + struct Letterbox { + Rect top; + Rect bottom; + Rect left; + Rect right; + } letterbox; +}; + +/** + * @brief 视口适配器类 + * + * 处理不同屏幕尺寸的适配,保持逻辑分辨率与屏幕分辨率的映射关系 + */ +class ViewportAdapter { +public: + ViewportAdapter(); + ViewportAdapter(float logicWidth, float logicHeight); + ~ViewportAdapter() = default; + + // 配置设置 + void setConfig(const ViewportConfig& config); + const ViewportConfig& getConfig() const { return config_; } + + void setLogicSize(float width, float height); + void setMode(ViewportMode mode); + void setLetterboxPosition(LetterboxPosition position); + void setLetterboxColor(const Color& color); + + // 更新和计算 + void update(int screenWidth, int screenHeight); + const ViewportResult& getResult() const { return result_; } + + // 坐标转换 + Vec2 screenToLogic(const Vec2& screenPos) const; + Vec2 logicToScreen(const Vec2& logicPos) const; + Vec2 screenToLogic(float x, float y) const; + Vec2 logicToScreen(float x, float y) const; + + // 矩阵获取 + glm::mat4 getMatrix() const; + glm::mat4 getInvMatrix() const; + + // 区域检测 + bool isInViewport(const Vec2& screenPos) const; + bool isInLetterbox(const Vec2& screenPos) const; + + // Getter 方法 + float getLogicWidth() const { return config_.logicWidth; } + float getLogicHeight() const { return config_.logicHeight; } + Size getLogicSize() const { return Size(config_.logicWidth, config_.logicHeight); } + + int getScreenWidth() const { return screenWidth_; } + int getScreenHeight() const { return screenHeight_; } + Size getScreenSize() const { return Size(static_cast(screenWidth_), static_cast(screenHeight_)); } + + float getScaleX() const { return result_.scaleX; } + float getScaleY() const { return result_.scaleY; } + float getUniformScale() const { return result_.uniformScale; } + Vec2 getOffset() const { return result_.offset; } + Rect getViewport() const { return result_.viewport; } + bool hasLetterbox() const { return result_.hasLetterbox; } + const ViewportResult::Letterbox& getLetterbox() const { return result_.letterbox; } + +private: + void calculateAspectRatio(); + void calculateStretch(); + void calculateCenter(); + void calculateCustom(); + void calculateLetterbox(); + void applyLetterboxPosition(float extraWidth, float extraHeight); + + ViewportConfig config_; + ViewportResult result_; + int screenWidth_ = 0; + int screenHeight_ = 0; + + mutable glm::mat4 viewportMatrix_; + mutable glm::mat4 inverseViewportMatrix_; + mutable bool matrixDirty_ = true; +}; + +} // namespace extra2d diff --git a/include/scene/component.h b/include/scene/component.h new file mode 100644 index 0000000..584173e --- /dev/null +++ b/include/scene/component.h @@ -0,0 +1,83 @@ +#pragma once + +#include + +namespace extra2d { + +// 前向声明 +class Node; + +/** + * @brief 组件基类 + * + * 所有组件都继承此类,附加到节点上提供特定功能 + */ +class Component : public RefCounted { +public: + /** + * @brief 虚析构函数 + */ + virtual ~Component() = default; + + /** + * @brief 组件附加到节点时调用 + * @param owner 所属节点 + */ + virtual void onAttach(Node* owner); + + /** + * @brief 组件从节点分离时调用 + */ + virtual void onDetach(); + + /** + * @brief 组件启用时调用 + */ + virtual void onEnable(); + + /** + * @brief 组件禁用时调用 + */ + virtual void onDisable(); + + /** + * @brief 每帧更新 + * @param dt 帧间隔时间(秒) + */ + virtual void update(float dt); + + /** + * @brief 渲染时调用(提交渲染命令) + */ + virtual void render(); + + /** + * @brief 获取所属节点 + * @return 节点指针 + */ + Node* getOwner() const { return owner_; } + + /** + * @brief 设置是否启用 + * @param enabled 是否启用 + */ + void setEnabled(bool enabled); + + /** + * @brief 是否启用 + * @return 是否启用 + */ + bool isEnabled() const { return enabled_; } + + /** + * @brief 获取组件类型名称 + * @return 类型名称字符串 + */ + virtual const char* getTypeName() const = 0; + +protected: + Node* owner_ = nullptr; + bool enabled_ = true; +}; + +} // namespace extra2d diff --git a/include/scene/components/camera_component.h b/include/scene/components/camera_component.h new file mode 100644 index 0000000..d38f1fd --- /dev/null +++ b/include/scene/components/camera_component.h @@ -0,0 +1,280 @@ +#pragma once + +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 相机组件 + * + * 负责管理相机投影和视图矩阵,支持缩放、旋转和边界限制 + */ +class CameraComponent : public Component { +public: + static constexpr const char* TYPE_NAME = "Camera"; + + /** + * @brief 投影类型 + */ + enum class ProjectionType { + Orthographic, // 正交投影 + Perspective // 透视投影 + }; + + /** + * @brief 构造函数 + */ + CameraComponent(); + + /** + * @brief 获取组件类型名称 + * @return 类型名称 + */ + const char* getTypeName() const override { return TYPE_NAME; } + + // ======================================== + // 投影设置 + // ======================================== + + /** + * @brief 设置投影类型 + * @param type 投影类型 + */ + void setProjectionType(ProjectionType type); + + /** + * @brief 获取投影类型 + * @return 投影类型 + */ + ProjectionType getProjectionType() const { return projType_; } + + /** + * @brief 设置正交投影 + * @param left 左边界 + * @param right 右边界 + * @param bottom 下边界 + * @param top 上边界 + * @param near 近裁剪面 + * @param far 远裁剪面 + */ + void setOrtho(float left, float right, float bottom, float top, float near, float far); + + /** + * @brief 设置透视投影 + * @param fov 视场角(度) + * @param aspect 宽高比 + * @param near 近裁剪面 + * @param far 远裁剪面 + */ + void setPerspective(float fov, float aspect, float near, float far); + + // ======================================== + // 变换属性(缩放、旋转) + // ======================================== + + /** + * @brief 设置缩放级别 + * @param zoom 缩放值(1.0为正常大小,>1放大,<1缩小) + */ + void setZoom(float zoom); + + /** + * @brief 获取缩放级别 + * @return 缩放值 + */ + float getZoom() const { return zoom_; } + + /** + * @brief 设置旋转角度 + * @param degrees 旋转角度(度数) + */ + void setRotation(float degrees); + + /** + * @brief 获取旋转角度 + * @return 旋转角度(度数) + */ + float getRotation() const { return rotation_; } + + // ======================================== + // 矩阵获取 + // ======================================== + + /** + * @brief 获取视图矩阵 + * @return 视图矩阵 + */ + Mat4 getViewMatrix() const; + + /** + * @brief 获取投影矩阵 + * @return 投影矩阵 + */ + Mat4 getProjectionMatrix() const; + + /** + * @brief 获取视图投影矩阵 + * @return 视图投影矩阵 + */ + Mat4 getViewProjectionMatrix() const; + + // ======================================== + // 坐标转换 + // ======================================== + + /** + * @brief 将屏幕坐标转换为世界坐标 + * @param screenPos 屏幕坐标 + * @return 世界坐标 + */ + Vec2 screenToWorld(const Vec2& screenPos) const; + + /** + * @brief 将屏幕坐标转换为世界坐标 + * @param x 屏幕X坐标 + * @param y 屏幕Y坐标 + * @return 世界坐标 + */ + Vec2 screenToWorld(float x, float y) const; + + /** + * @brief 将世界坐标转换为屏幕坐标 + * @param worldPos 世界坐标 + * @return 屏幕坐标 + */ + Vec2 worldToScreen(const Vec2& worldPos) const; + + /** + * @brief 将世界坐标转换为屏幕坐标 + * @param x 世界X坐标 + * @param y 世界Y坐标 + * @return 屏幕坐标 + */ + Vec2 worldToScreen(float x, float y) const; + + // ======================================== + // 视口 + // ======================================== + + /** + * @brief 设置视口 + * @param viewport 视口矩形 + */ + void setViewport(const Rect& viewport); + + /** + * @brief 获取视口 + * @return 视口矩形 + */ + Rect getViewport() const { return viewport_; } + + // ======================================== + // 边界限制 + // ======================================== + + /** + * @brief 设置相机边界限制 + * @param bounds 边界矩形 + */ + void setBounds(const Rect& bounds); + + /** + * @brief 清除相机边界限制 + */ + void clearBounds(); + + /** + * @brief 检查是否有边界限制 + * @return 是否有边界限制 + */ + bool hasBounds() const { return hasBounds_; } + + /** + * @brief 将相机位置限制在边界内 + */ + void clampToBounds(); + + // ======================================== + // 移动相机 + // ======================================== + + /** + * @brief 移动相机位置 + * @param offset 位置偏移量 + */ + void move(const Vec2& offset); + + /** + * @brief 移动相机位置 + * @param x X方向偏移量 + * @param y Y方向偏移量 + */ + void move(float x, float y); + + /** + * @brief 将相机移动到目标位置 + * @param target 目标位置 + */ + void lookAt(const Vec2& target); + +private: + /** + * @brief 标记视图矩阵为脏 + */ + void markViewDirty() { viewDirty_ = true; vpDirty_ = true; } + + /** + * @brief 标记投影矩阵为脏 + */ + void markProjDirty() { projDirty_ = true; vpDirty_ = true; } + + /** + * @brief 更新视图矩阵 + */ + void updateViewMatrix() const; + + /** + * @brief 更新投影矩阵 + */ + void updateProjectionMatrix() const; + + ProjectionType projType_ = ProjectionType::Orthographic; + + // 投影参数 + float left_ = 0.0f; + float right_ = 800.0f; + float bottom_ = 600.0f; + float top_ = 0.0f; + float near_ = -1.0f; + float far_ = 1.0f; + + // 透视投影参数 + float fov_ = 60.0f; + float aspect_ = 16.0f / 9.0f; + + // 变换属性 + float zoom_ = 1.0f; + float rotation_ = 0.0f; + + // 视口 + Rect viewport_; + + // 边界限制 + Rect bounds_; + bool hasBounds_ = false; + + // 缓存矩阵(mutable 用于 const 方法中延迟计算) + mutable Mat4 viewMatrix_ = Mat4(1.0f); + mutable Mat4 projMatrix_ = Mat4(1.0f); + mutable Mat4 vpMatrix_ = Mat4(1.0f); + + // 脏标记 + mutable bool viewDirty_ = true; + mutable bool projDirty_ = true; + mutable bool vpDirty_ = true; +}; + +} // namespace extra2d diff --git a/include/scene/components/sprite_renderer.h b/include/scene/components/sprite_renderer.h new file mode 100644 index 0000000..0189b8e --- /dev/null +++ b/include/scene/components/sprite_renderer.h @@ -0,0 +1,121 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { + +/** + * @brief 精灵渲染组件 + * + * 负责渲染2D精灵,通过事件提交渲染命令 + * + * 使用材质作为纹理和着色器的中间层: + * - 材质包含着色器、参数和纹理 + * - 如果只想使用默认着色器,可以设置纹理,组件会自动处理 + * + * 使用示例: + * @code + * // 方式1:使用完整材质(推荐) + * auto material = makePtr(); + * material->setShader(customShader); + * material->setTexture("uTexture", textureHandle, 0); + * material->setColor("uTintColor", Color::Red); + * MaterialHandle matHandle = renderer->registerMaterial(material); + * sprite->setMaterial(matHandle); + * + * // 方式2:只设置纹理(使用默认材质) + * sprite->setTexture(textureHandle); + * @endcode + */ +class SpriteRenderer : public Component { +public: + static constexpr const char* TYPE_NAME = "SpriteRenderer"; + + /** + * @brief 构造函数 + */ + SpriteRenderer(); + + /** + * @brief 获取组件类型名称 + * @return 类型名称 + */ + const char* getTypeName() const override { return TYPE_NAME; } + + /** + * @brief 组件附加到节点时调用 + * @param owner 所属节点 + */ + void onAttach(Node* owner) override; + + // ======================================== + // 材质设置(推荐方式) + // ======================================== + + /** + * @brief 设置材质 + * + * 材质包含着色器、参数和纹理 + * @param material 材质句柄 + */ + void setMaterial(MaterialHandle material); + + /** + * @brief 获取材质 + * @return 材质句柄 + */ + MaterialHandle getMaterial() const { return material_; } + + // ======================================== + // 纹理设置(便捷方式) + // ======================================== + + /** + * @brief 设置纹理 + * + * 如果没有设置材质,渲染时会使用默认材质并绑定此纹理 + * 如果设置了材质,此设置被忽略(材质中的纹理优先) + * @param texture 纹理句柄 + */ + void setTexture(TextureHandle texture); + + /** + * @brief 获取纹理 + * @return 纹理句柄 + */ + TextureHandle getTexture() const { return texture_; } + + // ======================================== + // 颜色(顶点颜色) + // ======================================== + + /** + * @brief 设置颜色 + * @param color 颜色 + */ + void setColor(const Color& color); + + /** + * @brief 获取颜色 + * @return 颜色 + */ + Color getColor() const { return color_; } + + // ======================================== + // 渲染 + // ======================================== + + /** + * @brief 渲染时调用 + */ + void render() override; + +private: + MaterialHandle material_ = INVALID_MATERIAL_HANDLE; // 材质句柄 + TextureHandle texture_ = INVALID_TEXTURE_HANDLE; // 纹理句柄(便捷方式) + Color color_ = Color::White; // 顶点颜色 +}; + +} // namespace extra2d diff --git a/include/scene/components/transform_component.h b/include/scene/components/transform_component.h new file mode 100644 index 0000000..c7814a4 --- /dev/null +++ b/include/scene/components/transform_component.h @@ -0,0 +1,167 @@ +#pragma once + +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 变换组件 + * + * 管理节点的位置、旋转、缩放和锚点 + * 支持世界变换计算和脏标记优化 + */ +class TransformComponent : public Component { +public: + static constexpr const char* TYPE_NAME = "Transform"; + + /** + * @brief 构造函数 + */ + TransformComponent(); + + /** + * @brief 获取组件类型名称 + * @return 类型名称 + */ + const char* getTypeName() const override { return TYPE_NAME; } + + // ======================================== + // 本地变换 + // ======================================== + + /** + * @brief 设置位置 + * @param pos 位置 + */ + void setPosition(const Vec2& pos); + + /** + * @brief 设置旋转角度 + * @param rot 角度(度) + */ + void setRotation(float rot); + + /** + * @brief 设置缩放 + * @param scale 缩放 + */ + void setScale(const Vec2& scale); + + /** + * @brief 获取位置 + * @return 位置 + */ + Vec2 getPosition() const { return local_.pos; } + + /** + * @brief 获取旋转角度 + * @return 角度(度) + */ + float getRotation() const { return local_.rot; } + + /** + * @brief 获取缩放 + * @return 缩放 + */ + Vec2 getScale() const { return local_.scale; } + + // ======================================== + // 锚点 + // ======================================== + + /** + * @brief 设置锚点 + * @param anchor 锚点 [0,1] + */ + void setAnchor(const Vec2& anchor); + + /** + * @brief 设置锚点 + * @param x X锚点 + * @param y Y锚点 + */ + void setAnchorPoint(float x, float y); + + /** + * @brief 获取锚点 + * @return 锚点 + */ + Vec2 getAnchor() const { return anchor_; } + + // ======================================== + // 尺寸 + // ======================================== + + /** + * @brief 设置尺寸 + * @param size 尺寸 + */ + void setSize(const Vec2& size); + + /** + * @brief 设置尺寸 + * @param width 宽度 + * @param height 高度 + */ + void setSize(float width, float height); + + /** + * @brief 获取尺寸 + * @return 尺寸 + */ + Vec2 getSize() const { return size_; } + + /** + * @brief 获取锚点偏移量(像素单位) + * @return 锚点偏移 + */ + Vec2 getAnchorOffset() const; + + // ======================================== + // 世界变换 + // ======================================== + + /** + * @brief 获取世界变换 + * @return 世界变换 + */ + Transform getWorldTransform() const; + + /** + * @brief 获取世界矩阵 + * @return 世界矩阵 + */ + Mat4 getWorldMatrix() const; + + // ======================================== + // 脏标记管理 + // ======================================== + + /** + * @brief 设置脏标记 + */ + void setDirty(); + + /** + * @brief 是否脏 + * @return 是否脏 + */ + bool isDirty() const { return dirty_; } + +private: + /** + * @brief 更新世界变换 + */ + void updateWorldTransform() const; + + Transform local_; + mutable Transform world_; + mutable bool dirty_ = true; + Vec2 anchor_ = Vec2(0.5f, 0.5f); + Vec2 size_ = Vec2(100.0f, 100.0f); +}; + +} // namespace extra2d diff --git a/include/scene/director.h b/include/scene/director.h new file mode 100644 index 0000000..52b7c72 --- /dev/null +++ b/include/scene/director.h @@ -0,0 +1,132 @@ +#pragma once + +#include +#include +#include +#include + +namespace extra2d { + +// 前向声明 +class CameraComponent; + +/** + * @brief 导演类 + * + * 管理场景的生命周期和切换 + */ +class Director : public RefCounted { +public: + /** + * @brief 构造函数 + */ + Director(); + + /** + * @brief 析构函数 + */ + ~Director() override; + + // 禁止拷贝 + Director(const Director&) = delete; + Director& operator=(const Director&) = delete; + + // ======================================== + // 生命周期 + // ======================================== + + /** + * @brief 初始化导演 + * @return 是否成功 + */ + bool init(); + + /** + * @brief 关闭导演 + */ + void shutdown(); + + // ======================================== + // 场景管理 + // ======================================== + + /** + * @brief 运行场景 + * @param scene 场景 + */ + void runScene(Ptr scene); + + /** + * @brief 替换当前场景 + * @param scene 新场景 + */ + void replaceScene(Ptr scene); + + /** + * @brief 压入场景(场景栈) + * @param scene 场景 + */ + void pushScene(Ptr scene); + + /** + * @brief 弹出场景(场景栈) + */ + void popScene(); + + /** + * @brief 结束当前场景 + */ + void end(); + + // ======================================== + // 属性获取 + // ======================================== + + /** + * @brief 获取当前运行的场景 + * @return 场景指针 + */ + Scene* getRunningScene() const; + + /** + * @brief 获取主相机 + * @return 相机组件指针 + */ + CameraComponent* getMainCamera() const; + + /** + * @brief 是否正在运行场景 + * @return 是否运行中 + */ + bool isRunning() const { return running_; } + + // ======================================== + // 生命周期 + // ======================================== + + /** + * @brief 更新当前场景 + * @param dt 帧间隔时间 + */ + void update(float dt); + + /** + * @brief 渲染当前场景 + */ + void render(); + + /** + * @brief 设置主相机 + * @param camera 相机组件 + */ + void setMainCamera(CameraComponent* camera) { mainCamera_ = camera; } + +private: + Ptr runningScene_; + std::stack> sceneStack_; + CameraComponent* mainCamera_ = nullptr; + bool running_ = false; + bool needEnd_ = false; +}; + +} // namespace extra2d diff --git a/include/scene/node.h b/include/scene/node.h new file mode 100644 index 0000000..24f6df4 --- /dev/null +++ b/include/scene/node.h @@ -0,0 +1,322 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +// 前向声明 +class Scene; +class TransformComponent; + +/** + * @brief 场景节点类 + * + * 节点是场景图的基本单元,可以包含多个组件和子节点 + */ +class Node : public RefCounted { +public: + /** + * @brief 构造函数 + */ + Node(); + + /** + * @brief 析构函数 + */ + ~Node() override; + + // 禁止拷贝 + Node(const Node&) = delete; + Node& operator=(const Node&) = delete; + + // ======================================== + // 层级关系 + // ======================================== + + /** + * @brief 添加子节点 + * @param child 子节点 + */ + void addChild(Ptr child); + + /** + * @brief 移除子节点 + * @param child 子节点 + */ + void removeChild(Node* child); + + /** + * @brief 从父节点移除 + */ + void removeFromParent(); + + /** + * @brief 获取父节点 + * @return 父节点指针 + */ + Node* getParent() const { return parent_; } + + /** + * @brief 获取所有子节点 + * @return 子节点列表 + */ + const std::vector>& getChildren() const { return children_; } + + /** + * @brief 移除所有子节点 + */ + void removeAllChildren(); + + // ======================================== + // 组件管理 + // ======================================== + + /** + * @brief 添加组件 + * @tparam T 组件类型 + * @param component 组件 + * @return 组件指针 + */ + template + T* addComponent(Ptr component) { + static_assert(std::is_base_of::value, "T must be derived from Component"); + T* ptr = component.get(); + components_.push_back(component); + ptr->onAttach(this); + + // 如果是 TransformComponent,缓存它 + if (auto* transform = dynamic_cast(ptr)) { + transform_ = transform; + } + + return ptr; + } + + /** + * @brief 获取组件 + * @tparam T 组件类型 + * @return 组件指针,未找到返回 nullptr + */ + template + T* getComponent() const { + for (const auto& comp : components_) { + if (auto* ptr = dynamic_cast(comp.get())) { + return ptr; + } + } + return nullptr; + } + + /** + * @brief 移除组件 + * @tparam T 组件类型 + */ + template + void removeComponent() { + for (auto it = components_.begin(); it != components_.end(); ++it) { + if (dynamic_cast(it->get())) { + (*it)->onDetach(); + components_.erase(it); + return; + } + } + } + + // ======================================== + // 变换便捷接口 + // ======================================== + + /** + * @brief 设置位置 + * @param pos 位置 + */ + void setPosition(const Vec2& pos); + + /** + * @brief 设置位置 + * @param x X坐标 + * @param y Y坐标 + */ + void setPosition(float x, float y); + + /** + * @brief 获取位置 + * @return 位置 + */ + Vec2 getPosition() const; + + /** + * @brief 设置旋转角度 + * @param rot 角度(度) + */ + void setRotation(float rot); + + /** + * @brief 获取旋转角度 + * @return 角度(度) + */ + float getRotation() const; + + /** + * @brief 设置缩放 + * @param scale 缩放 + */ + void setScale(const Vec2& scale); + + /** + * @brief 设置统一缩放 + * @param scale 缩放值 + */ + void setScale(float scale); + + /** + * @brief 设置缩放 + * @param x X轴缩放 + * @param y Y轴缩放 + */ + void setScale(float x, float y); + + /** + * @brief 获取缩放 + * @return 缩放 + */ + Vec2 getScale() const; + + /** + * @brief 设置尺寸 + * @param size 尺寸 + */ + void setSize(const Vec2& size); + + /** + * @brief 设置尺寸 + * @param width 宽度 + * @param height 高度 + */ + void setSize(float width, float height); + + /** + * @brief 获取尺寸 + * @return 尺寸 + */ + Vec2 getSize() const; + + /** + * @brief 设置锚点 + * @param anchor 锚点 [0,1] + */ + void setAnchor(const Vec2& anchor); + + /** + * @brief 设置锚点 + * @param x X锚点 + * @param y Y锚点 + */ + void setAnchor(float x, float y); + + /** + * @brief 获取锚点 + * @return 锚点 + */ + Vec2 getAnchor() const; + + /** + * @brief 获取世界变换 + * @return 世界变换 + */ + Transform getWorldTransform() const; + + /** + * @brief 获取世界矩阵 + * @return 世界矩阵 + */ + Mat4 getWorldMatrix() const; + + // ======================================== + // 属性 + // ======================================== + + /** + * @brief 设置名称 + * @param name 名称 + */ + void setName(const std::string& name) { name_ = name; } + + /** + * @brief 获取名称 + * @return 名称 + */ + const std::string& getName() const { return name_; } + + /** + * @brief 设置标签 + * @param tag 标签 + */ + void setTag(int32 tag) { tag_ = tag; } + + /** + * @brief 获取标签 + * @return 标签 + */ + int32 getTag() const { return tag_; } + + /** + * @brief 设置是否可见 + * @param visible 是否可见 + */ + void setVisible(bool visible) { visible_ = visible; } + + /** + * @brief 是否可见 + * @return 是否可见 + */ + bool isVisible() const { return visible_; } + + /** + * @brief 获取 TransformComponent + * @return TransformComponent 指针 + */ + TransformComponent* getTransform() const { return transform_; } + + // ======================================== + // 生命周期 + // ======================================== + + /** + * @brief 节点进入场景时调用 + */ + void onEnter(); + + /** + * @brief 节点离开场景时调用 + */ + void onExit(); + + /** + * @brief 每帧更新 + * @param dt 帧间隔时间 + */ + void update(float dt); + + /** + * @brief 渲染(收集渲染命令) + */ + void render(); + +private: + std::string name_; + int32 tag_ = 0; + bool visible_ = true; + Node* parent_ = nullptr; + std::vector> children_; + std::vector> components_; + TransformComponent* transform_ = nullptr; +}; + +} // namespace extra2d diff --git a/include/scene/scene.h b/include/scene/scene.h new file mode 100644 index 0000000..dea5f9b --- /dev/null +++ b/include/scene/scene.h @@ -0,0 +1,122 @@ +#pragma once + +#include +#include +#include +#include + +namespace extra2d { + +// 前向声明 +class CameraComponent; + +/** + * @brief 场景类 + * + * 场景是游戏内容的容器,管理所有根节点和相机 + */ +class Scene : public RefCounted { +public: + /** + * @brief 构造函数 + */ + Scene(); + + /** + * @brief 析构函数 + */ + ~Scene() override; + + // 禁止拷贝 + Scene(const Scene&) = delete; + Scene& operator=(const Scene&) = delete; + + // ======================================== + // 节点管理 + // ======================================== + + /** + * @brief 添加子节点 + * @param node 节点 + */ + void addChild(Ptr node); + + /** + * @brief 移除子节点 + * @param node 节点 + */ + void removeChild(Node* node); + + /** + * @brief 移除所有子节点 + */ + void removeAllChildren(); + + /** + * @brief 获取所有子节点 + * @return 子节点列表 + */ + const std::vector>& getChildren() const { return rootNodes_; } + + /** + * @brief 根据名称查找节点 + * @param name 节点名称 + * @return 节点指针,未找到返回 nullptr + */ + Node* findNode(const std::string& name); + + /** + * @brief 根据标签查找节点 + * @param tag 节点标签 + * @return 节点指针,未找到返回 nullptr + */ + Node* findNodeByTag(int32 tag); + + // ======================================== + // 相机管理 + // ======================================== + + /** + * @brief 设置主相机 + * @param camera 相机组件 + */ + void setMainCamera(CameraComponent* camera); + + /** + * @brief 获取主相机 + * @return 相机组件指针 + */ + CameraComponent* getMainCamera() const { return mainCamera_; } + + // ======================================== + // 生命周期 + // ======================================== + + /** + * @brief 场景进入时调用 + */ + virtual void onEnter(); + + /** + * @brief 场景退出时调用 + */ + virtual void onExit(); + + /** + * @brief 每帧更新 + * @param dt 帧间隔时间 + */ + virtual void update(float dt); + + /** + * @brief 渲染场景 + */ + virtual void render(); + +protected: + std::vector> rootNodes_; + CameraComponent* mainCamera_ = nullptr; + bool entered_ = false; +}; + +} // namespace extra2d diff --git a/include/scene/scene_module.h b/include/scene/scene_module.h new file mode 100644 index 0000000..aa6fcfc --- /dev/null +++ b/include/scene/scene_module.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 场景模块 + * + * 将导演系统集成到引擎模块系统中 + */ +class SceneModule : public Module { + // 自动注册到模块系统,优先级为 4(在 Renderer 之后) + E2D_REGISTER_MODULE(SceneModule, "Scene", 4) + +public: + SceneModule(); + ~SceneModule() override; + + // 禁止拷贝 + SceneModule(const SceneModule &) = delete; + SceneModule &operator=(const SceneModule &) = delete; + + // 允许移动 + SceneModule(SceneModule &&) noexcept; + SceneModule &operator=(SceneModule &&) noexcept; + + // Module 接口实现 + bool init() override; + void shutdown() override; + + /** + * @brief 获取导演 + * @return 导演指针 + */ + Director *getDirector() const { return director_.get(); } + +private: + Ptr director_; + + // 事件监听器 + events::OnUpdate::Listener onUpdateListener_; + events::OnRenderBegin::Listener onRenderBeginListener_; + events::OnRenderEnd::Listener onRenderEndListener_; +}; + +} // namespace extra2d diff --git a/include/types/math/mat4.h b/include/types/math/mat4.h new file mode 100644 index 0000000..4618620 --- /dev/null +++ b/include/types/math/mat4.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +namespace extra2d { + +/** + * @brief 4x4 矩阵类型 + * + * 使用 glm::mat4 作为底层实现 + */ +using Mat4 = glm::mat4; + +} // namespace extra2d diff --git a/include/types/math/transform.h b/include/types/math/transform.h index e3c11a3..449d16c 100644 --- a/include/types/math/transform.h +++ b/include/types/math/transform.h @@ -2,6 +2,9 @@ #include #include +#include +#include +#include #include namespace extra2d { @@ -27,18 +30,28 @@ struct Transform { bool operator!=(const Transform& o) const { return !(*this == o); } /** - * @brief 转换为 4x4 矩阵(列主序) + * @brief 转换为 4x4 矩阵(列主序),使用 glm 构建 * @param out 输出矩阵(16个float) + * + * 变换顺序:缩放 -> 旋转 -> 平移 + * 对于列向量,变换从右向左应用:v' = T * R * S * v */ void toMatrix(float* out) const { - float c = std::cos(rot); - float s = std::sin(rot); + // 使用 glm 构建矩阵 + // glm::translate(matrix, v) 返回 matrix * T + // 所以顺序是:先平移,再旋转,最后缩放 + // 结果是 T * R * S,对于列向量 v:v' = T * R * S * v + // 这意味着先应用 S,然后 R,然后 T(正确的顺序) + glm::mat4 matrix = glm::mat4(1.0f); + matrix = glm::translate(matrix, glm::vec3(pos.x, pos.y, 0.0f)); + matrix = glm::rotate(matrix, rot, glm::vec3(0.0f, 0.0f, 1.0f)); + matrix = glm::scale(matrix, glm::vec3(scale.x, scale.y, 1.0f)); - // 列主序矩阵 - out[0] = scale.x * c; out[1] = scale.x * s; out[2] = 0.0f; out[3] = 0.0f; - out[4] = -scale.y * s; out[5] = scale.y * c; out[6] = 0.0f; out[7] = 0.0f; - out[8] = 0.0f; out[9] = 0.0f; out[10] = 1.0f; out[11] = 0.0f; - out[12] = pos.x; out[13] = pos.y; out[14] = 0.0f; out[15] = 1.0f; + // 复制到输出数组(glm 已经是列主序) + const float* src = glm::value_ptr(matrix); + for (int i = 0; i < 16; ++i) { + out[i] = src[i]; + } } static const Transform Identity; diff --git a/shader/default.frag b/shader/default.frag index 77d0444..19a4104 100644 --- a/shader/default.frag +++ b/shader/default.frag @@ -1,31 +1,36 @@ #version 320 es -precision mediump float; +precision highp float; -// 材质 UBO -layout(std140, binding = 1) uniform MaterialUBO { - vec4 tintColor; - float opacity; -} uMaterial; - -// 输入从顶点着色器 +// 从顶点着色器输入 in vec2 vTexCoord; in vec4 vColor; // 纹理采样器 uniform sampler2D uTexture; +// 材质参数 +uniform vec4 uTintColor; +uniform float uOpacity; + // 输出颜色 out vec4 fragColor; +/** + * @brief 片段着色器入口 + * + * 采样纹理并与顶点颜色、色调和透明度混合 + */ void main() { // 采样纹理 vec4 texColor = texture(uTexture, vTexCoord); - // 应用顶点颜色、材质颜色和透明度 - fragColor = texColor * vColor * uMaterial.tintColor; - fragColor.a *= uMaterial.opacity; + // 混合:纹理 * 顶点颜色 * 色调 + fragColor = texColor * vColor * uTintColor; - // 丢弃完全透明的像素 + // 应用透明度 + fragColor.a *= uOpacity; + + // Alpha 测试:丢弃几乎透明的像素 if (fragColor.a < 0.01) { discard; } diff --git a/shader/default.vert b/shader/default.vert index 9f9dc46..495259b 100644 --- a/shader/default.vert +++ b/shader/default.vert @@ -1,16 +1,14 @@ #version 320 es +precision highp float; -// 全局 UBO -layout(std140, binding = 0) uniform GlobalUBO { - mat4 viewProjection; - vec2 screenSize; - float time; -} uGlobal; +// 视图投影矩阵 +uniform mat4 uViewProjection; -// 实例 UBO -layout(std140, binding = 2) uniform InstanceUBO { - mat4 transforms[256]; -} uInstances; +// 模型矩阵 +uniform mat4 uModelMatrix; + +// 顶点颜色(覆盖顶点属性中的颜色) +uniform vec4 uColor; // 顶点属性 layout(location = 0) in vec2 aPosition; @@ -21,14 +19,15 @@ layout(location = 2) in vec4 aColor; out vec2 vTexCoord; out vec4 vColor; +/** + * @brief 顶点着色器入口 + * + * 计算顶点在裁剪空间中的位置, + * 并传递纹理坐标和顶点颜色到片段着色器 + */ void main() { - // 获取实例变换矩阵 - mat4 instanceTransform = uInstances.transforms[gl_InstanceID]; - - // 计算最终位置 - gl_Position = uGlobal.viewProjection * instanceTransform * vec4(aPosition, 0.0, 1.0); - - // 传递纹理坐标和颜色 + gl_Position = uViewProjection * uModelMatrix * vec4(aPosition, 0.0, 1.0); vTexCoord = aTexCoord; - vColor = aColor; + // 使用 uniform 颜色覆盖顶点属性颜色 + vColor = uColor; } diff --git a/src/context/context.cpp b/src/context/context.cpp index a343994..84fcb80 100644 --- a/src/context/context.cpp +++ b/src/context/context.cpp @@ -79,6 +79,9 @@ void Context::tick(float dt) { // 3. 渲染结束并执行绘制 events::OnRenderEnd::emit(); + + // 4. 呈现渲染结果(交换缓冲区) + events::OnRenderPresent::emit(); } ModuleRegistry &Context::modules() { return ModuleRegistry::instance(); } diff --git a/src/platform/window_module.cpp b/src/platform/window_module.cpp index 0981073..6eb3d5a 100644 --- a/src/platform/window_module.cpp +++ b/src/platform/window_module.cpp @@ -49,6 +49,11 @@ bool WindowModule::init() { configListener_->bind( [this](const AppConfig &config) { this->onModuleConfig(config); }); + // 监听渲染呈现事件 + renderPresentListener_ = + std::make_unique(); + renderPresentListener_->bind([this]() { this->onRenderPresent(); }); + return true; } @@ -90,6 +95,7 @@ bool WindowModule::pollEvents() { while (SDL_PollEvent(&evt)) { switch (evt.type) { case SDL_QUIT: + E2D_LOG_INFO("SDL_QUIT event received"); shouldClose_ = true; // 发送窗口关闭事件 events::OnClose::emit(); @@ -280,6 +286,10 @@ void WindowModule::swapBuffers() { } } +void WindowModule::onRenderPresent() { + swapBuffers(); +} + void *WindowModule::getGLContext() const { return glContext_; } } // namespace extra2d diff --git a/src/renderer/material.cpp b/src/renderer/material.cpp index cf95de2..40b3d14 100644 --- a/src/renderer/material.cpp +++ b/src/renderer/material.cpp @@ -4,10 +4,17 @@ namespace extra2d { +// ======================================== // MaterialLayout 实现 +// ======================================== MaterialLayout::MaterialLayout() = default; +/** + * @brief 添加参数到布局 + * @param name 参数名称 + * @param type 参数类型 + */ void MaterialLayout::addParam(const std::string& name, MaterialParamType type) { if (finalized_) { E2D_LOG_WARN("Cannot add param to finalized MaterialLayout"); @@ -22,27 +29,42 @@ void MaterialLayout::addParam(const std::string& name, MaterialParamType type) { params_[name] = info; } +/** + * @brief 完成布局定义,计算 std140 布局偏移 + * + * std140 布局规则: + * - 标量和向量:偏移必须是其大小的倍数 + * - 数组和结构体:偏移必须是 16 的倍数 + * - vec3 后面需要填充到 16 字节边界 + */ void MaterialLayout::finalize() { if (finalized_) return; - // 计算 std140 布局的偏移 - // std140 规则: - // - 标量和向量:偏移必须是其大小的倍数 - // - 数组和结构体:偏移必须是 16 的倍数 - // - vec3 后面需要填充到 16 字节边界 - uint32_t offset = 0; for (auto& pair : params_) { auto& info = pair.second; - // 对齐到参数大小的倍数 - uint32_t alignment = info.size; - if (info.type == MaterialParamType::Mat4) { - alignment = 16; // mat4 需要 16 字节对齐 - } else if (info.type == MaterialParamType::Vec3) { - alignment = 16; // vec3 在 std140 中占 16 字节 - } else if (alignment < 4) { - alignment = 4; // 最小 4 字节对齐 + // 计算对齐要求 + uint32_t alignment = 4; // 默认 4 字节对齐 + + switch (info.type) { + case MaterialParamType::Float: + alignment = 4; // float: 4 字节对齐 + break; + case MaterialParamType::Vec2: + alignment = 8; // vec2: 8 字节对齐 + break; + case MaterialParamType::Vec3: + case MaterialParamType::Vec4: + case MaterialParamType::Color: + alignment = 16; // vec3/vec4/color: 16 字节对齐(std140 规则) + break; + case MaterialParamType::Mat4: + alignment = 16; // mat4: 16 字节对齐 + break; + default: + alignment = 4; + break; } // 对齐偏移 @@ -57,6 +79,11 @@ void MaterialLayout::finalize() { finalized_ = true; } +/** + * @brief 获取参数信息 + * @param name 参数名称 + * @return 参数信息指针,不存在返回 nullptr + */ const MaterialParamInfo* MaterialLayout::getParam(const std::string& name) const { auto it = params_.find(name); if (it != params_.end()) { @@ -65,10 +92,16 @@ const MaterialParamInfo* MaterialLayout::getParam(const std::string& name) const return nullptr; } +// ======================================== // Material 实现 +// ======================================== Material::Material() = default; +/** + * @brief 设置材质布局 + * @param layout 材质布局 + */ void Material::setLayout(Ptr layout) { layout_ = layout; if (layout_ && layout_->isFinalized()) { @@ -76,10 +109,19 @@ void Material::setLayout(Ptr layout) { } } +/** + * @brief 设置着色器 + * @param shader 着色器 + */ void Material::setShader(Ptr shader) { shader_ = shader; } +/** + * @brief 设置 float 参数 + * @param name 参数名称 + * @param value 值 + */ void Material::setFloat(const std::string& name, float value) { if (!layout_) return; @@ -89,6 +131,11 @@ void Material::setFloat(const std::string& name, float value) { } } +/** + * @brief 设置 vec2 参数 + * @param name 参数名称 + * @param value 值 + */ void Material::setVec2(const std::string& name, const Vec2& value) { if (!layout_) return; @@ -98,6 +145,14 @@ void Material::setVec2(const std::string& name, const Vec2& value) { } } +/** + * @brief 设置 vec4 参数 + * @param name 参数名称 + * @param x X 分量 + * @param y Y 分量 + * @param z Z 分量 + * @param w W 分量 + */ void Material::setVec4(const std::string& name, float x, float y, float z, float w) { if (!layout_) return; @@ -108,6 +163,11 @@ void Material::setVec4(const std::string& name, float x, float y, float z, float } } +/** + * @brief 设置颜色参数 + * @param name 参数名称 + * @param value 颜色值 + */ void Material::setColor(const std::string& name, const Color& value) { if (!layout_) return; @@ -117,6 +177,11 @@ void Material::setColor(const std::string& name, const Color& value) { } } +/** + * @brief 设置 mat4 参数 + * @param name 参数名称 + * @param value 矩阵数据指针 + */ void Material::setMat4(const std::string& name, const float* value) { if (!layout_) return; @@ -126,17 +191,50 @@ void Material::setMat4(const std::string& name, const float* value) { } } -void Material::setTexture(const std::string& name, TextureHandle texture, uint32_t slot) { - // 纹理存储在单独的列表中 - textures_.push_back({texture, slot}); +/** + * @brief 设置纹理 + * @param uniformName 着色器中的采样器 uniform 名称 + * @param texture 纹理句柄 + * @param slot 纹理槽位(0-15) + */ +void Material::setTexture(const std::string& uniformName, TextureHandle texture, uint32_t slot) { + // 查找是否已存在相同名称的纹理 + for (auto& texSlot : textures_) { + if (texSlot.uniformName == uniformName) { + texSlot.handle = texture; + texSlot.slot = slot; + return; + } + } + + // 添加新的纹理槽位 + textures_.push_back({texture, slot, uniformName}); } +/** + * @brief 清除所有纹理 + */ +void Material::clearTextures() { + textures_.clear(); +} + +/** + * @brief 应用材质 + * + * 绑定着色器、上传 UBO 数据 + * @param uboManager UBO 管理器 + */ void Material::apply(UniformBufferManager& uboManager) { if (!shader_) return; // 绑定着色器 shader_->bind(); + // 设置 Uniform Block 绑定 + shader_->setUniformBlock("GlobalUBO", GLOBAL_UBO_BINDING); + shader_->setUniformBlock("MaterialUBO", MATERIAL_UBO_BINDING); + shader_->setUniformBlock("InstanceUBO", INSTANCE_UBO_BINDING); + // 上传材质数据到 UBO if (!data_.empty()) { auto* ubo = uboManager.acquireMaterialUBO(static_cast(data_.size())); @@ -145,14 +243,6 @@ void Material::apply(UniformBufferManager& uboManager) { ubo->bind(MATERIAL_UBO_BINDING); } } - - // 设置 Uniform Block 绑定(如果着色器支持) - shader_->setUniformBlock("MaterialUBO", MATERIAL_UBO_BINDING); - - // TODO: 绑定纹理 - // for (const auto& [texture, slot] : textures_) { - // // 通过纹理句柄获取纹理并绑定 - // } } } // namespace extra2d diff --git a/src/renderer/mesh.cpp b/src/renderer/mesh.cpp index 25ce619..ad23e7f 100644 --- a/src/renderer/mesh.cpp +++ b/src/renderer/mesh.cpp @@ -25,6 +25,8 @@ void Mesh::setVertices(const Vertex* vertices, uint32_t count) { if (vao_ == 0) { glGenVertexArrays(1, &vao_); glGenBuffers(1, &vbo_); + + E2D_LOG_INFO("Created VAO: {}, VBO: {}", vao_, vbo_); } glBindVertexArray(vao_); @@ -56,6 +58,8 @@ void Mesh::setVertices(const Vertex* vertices, uint32_t count) { glBindVertexArray(0); vertexCount_ = count; + + E2D_LOG_INFO("Set {} vertices for VAO {}", count, vao_); } void Mesh::setIndices(const uint16_t* indices, uint32_t count) { @@ -102,11 +106,31 @@ void Mesh::unbind() const { void Mesh::draw() const { if (vao_ == 0) return; + // 检查 OpenGL 错误 + GLenum err = glGetError(); + if (err != GL_NO_ERROR) { + static bool loggedOnce = false; + if (!loggedOnce) { + E2D_LOG_ERROR("OpenGL error before draw: {}", err); + loggedOnce = true; + } + } + if (indexCount_ > 0) { glDrawElements(GL_TRIANGLES, indexCount_, GL_UNSIGNED_SHORT, nullptr); } else { glDrawArrays(GL_TRIANGLES, 0, vertexCount_); } + + // 检查绘制后的错误 + err = glGetError(); + if (err != GL_NO_ERROR) { + static bool loggedOnce2 = false; + if (!loggedOnce2) { + E2D_LOG_ERROR("OpenGL error after draw: {}", err); + loggedOnce2 = true; + } + } } void Mesh::drawInstanced(uint32_t instanceCount) const { diff --git a/src/renderer/renderer_module.cpp b/src/renderer/renderer_module.cpp index 0032677..a7d6e53 100644 --- a/src/renderer/renderer_module.cpp +++ b/src/renderer/renderer_module.cpp @@ -1,8 +1,12 @@ #include #include +#include #include #include +// SDL for window size query +#include + namespace extra2d { RendererModule::RendererModule() = default; @@ -93,6 +97,8 @@ bool RendererModule::init() { onRenderBeginListener_.bind([this]() { onRenderBegin(); }); onRenderSubmitListener_.bind( [this](const RenderCommand &cmd) { onRenderSubmit(cmd); }); + onRenderSetCameraListener_.bind( + [this](const Mat4 &viewProj) { onRenderSetCamera(viewProj); }); onRenderEndListener_.bind([this]() { onRenderEnd(); }); onResizeListener_.bind([this](int32_t w, int32_t h) { onResize(w, h); }); @@ -122,11 +128,23 @@ void RendererModule::onWindowShow() { return; } - // 设置默认视口 - setViewport(0, 0, 800, 600); + // 获取实际窗口大小并设置视口 + // 查询当前 SDL 窗口大小 + int windowWidth = 800, windowHeight = 600; + SDL_Window *sdlWindow = SDL_GL_GetCurrentWindow(); + if (sdlWindow) { + SDL_GetWindowSize(sdlWindow, &windowWidth, &windowHeight); + E2D_LOG_INFO("Setting initial viewport to window size: {}x{}", windowWidth, + windowHeight); + } else { + E2D_LOG_WARN("Could not get SDL window, using default viewport 800x600"); + } + setViewport(0, 0, static_cast(windowWidth), + static_cast(windowHeight)); - // 启用深度测试和混合 - glEnable(GL_DEPTH_TEST); + // 禁用深度测试和背面剔除(2D渲染不需要) + // glDisable(GL_DEPTH_TEST); + // glDisable(GL_CULL_FACE); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -223,7 +241,16 @@ void RendererModule::setViewport(int32 x, int32 y, int32 width, int32 height) { viewportY_ = y; viewportWidth_ = width; viewportHeight_ = height; - glViewport(x, y, width, height); + + // 更新视口适配器 + viewportAdapter_.update(width, height); + + // 获取适配后的视口 + auto result = viewportAdapter_.getResult(); + glViewport(static_cast(result.viewport.x), + static_cast(result.viewport.y), + static_cast(result.viewport.w), + static_cast(result.viewport.h)); } void RendererModule::clear(const Color &color, uint32_t flags) { @@ -253,6 +280,10 @@ void RendererModule::onRenderBegin() { return; } + // 清除屏幕(黑色背景) + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + // 清空命令缓冲区 commandCount_ = 0; @@ -280,6 +311,19 @@ void RendererModule::onRenderSubmit(const RenderCommand &cmd) { stats_.commandsSubmitted++; } +void RendererModule::onRenderSetCamera(const Mat4 &viewProj) { + // 如果 GL 未初始化,跳过 + if (!glInitialized_) { + return; + } + + // 保存视图投影矩阵 + viewProjectionMatrix_ = viewProj; + + // 更新全局 UBO 中的视图投影矩阵 + uniformManager_.updateGlobalUBO(&viewProj, sizeof(Mat4)); +} + void RendererModule::onRenderEnd() { // 如果 GL 未初始化,跳过渲染 if (!glInitialized_) { @@ -361,50 +405,81 @@ void RendererModule::drawBatch(uint32_t start, uint32_t count, MaterialHandle materialHandle, MeshHandle meshHandle) { auto material = getMaterial(materialHandle); + // 如果材质无效,使用默认材质 + if (!material) { + material = getMaterial(defaultMaterialHandle_); + } + auto mesh = getMesh(meshHandle); + // 如果网格无效,使用默认四边形 + if (!mesh) { + mesh = getMesh(defaultQuadHandle_); + } if (!material || !mesh) return; - // 应用材质(绑定着色器、上传 UBO、绑定纹理) - material->apply(uniformManager_); + // 获取着色器 + auto shader = material->getShader(); + if (!shader) + return; + + // 绑定着色器 + shader->bind(); + + // 设置视图投影矩阵 + shader->setMat4("uViewProjection", glm::value_ptr(viewProjectionMatrix_)); + + // 设置材质参数 + // 注意:直接使用默认值,因为材质数据布局可能不匹配 + // tintColor 和 opacity 由着色器默认值处理 + shader->setVec4("uTintColor", 1.0f, 1.0f, 1.0f, 1.0f); + shader->setFloat("uOpacity", 1.0f); + + // 绑定材质中的纹理 + const auto &textureSlots = material->getTextures(); + bool hasMaterialTexture = false; + for (const auto &slot : textureSlots) { + if (slot.handle != INVALID_TEXTURE_HANDLE) { + auto texture = getTexture(slot.handle); + if (texture) { + texture->bind(slot.slot); + // 设置采样器 uniform + shader->setInt(slot.uniformName, static_cast(slot.slot)); + hasMaterialTexture = true; + } + } + } + + // 如果材质没有纹理,绑定默认纹理 + if (!hasMaterialTexture && defaultTextureHandle_ != INVALID_TEXTURE_HANDLE) { + auto defaultTexture = getTexture(defaultTextureHandle_); + if (defaultTexture) { + defaultTexture->bind(0); + shader->setInt("uTexture", 0); + } + } // 绑定网格 mesh->bind(); - if (count == 1) { - // 单绘制 + // 对每个实例单独绘制 + for (uint32_t i = 0; i < count; ++i) { + Transform transform = commandBuffer_[start + i].getTransform(); + float matrix[16]; + transform.toMatrix(matrix); + + // 设置模型矩阵 + shader->setMat4("uModelMatrix", matrix); + + // 设置顶点颜色 + Color color = commandBuffer_[start + i].getColor(); + shader->setVec4("uColor", color.r, color.g, color.b, color.a); + + // 绘制 mesh->draw(); - } else { - // 批量绘制 - 使用实例化渲染 - // 上传变换矩阵到 UBO - std::vector transforms; - transforms.reserve(count * 16); - - for (uint32_t i = 0; i < count; ++i) { - const auto &transform = commandBuffer_[start + i].drawMesh.transform; - // 将 Transform 转换为 4x4 矩阵并添加到 transforms - // 这里简化处理,实际需要完整的矩阵转换 - float matrix[16]; - transform.toMatrix(matrix); - transforms.insert(transforms.end(), matrix, matrix + 16); - } - - // 更新实例 UBO - auto *instanceUBO = uniformManager_.acquireMaterialUBO( - static_cast(transforms.size() * sizeof(float))); - if (instanceUBO) { - instanceUBO->update( - transforms.data(), - static_cast(transforms.size() * sizeof(float))); - instanceUBO->bind(INSTANCE_UBO_BINDING); - } - - // 实例化渲染 - mesh->drawInstanced(count); + stats_.drawCalls++; } - - stats_.drawCalls++; } void RendererModule::executeCommand(const RenderCommand &cmd) { diff --git a/src/renderer/shader.cpp b/src/renderer/shader.cpp index 1dc0b8f..3c659d1 100644 --- a/src/renderer/shader.cpp +++ b/src/renderer/shader.cpp @@ -52,21 +52,27 @@ bool Shader::loadFromSource(const std::string& vsSource, const std::string& fsSo std::string processedVS = addVersionIfNeeded(vsSource, GL_VERTEX_SHADER); std::string processedFS = addVersionIfNeeded(fsSource, GL_FRAGMENT_SHADER); + E2D_LOG_INFO("Compiling vertex shader..."); // 编译顶点着色器 GLuint vs = compileShader(GL_VERTEX_SHADER, processedVS); if (vs == 0) { + E2D_LOG_ERROR("Vertex shader compilation failed"); return false; } + E2D_LOG_INFO("Compiling fragment shader..."); // 编译片段着色器 GLuint fs = compileShader(GL_FRAGMENT_SHADER, processedFS); if (fs == 0) { + E2D_LOG_ERROR("Fragment shader compilation failed"); glDeleteShader(vs); return false; } + E2D_LOG_INFO("Linking shader program..."); // 链接程序 if (!linkProgram(vs, fs)) { + E2D_LOG_ERROR("Shader program linking failed"); glDeleteShader(vs); glDeleteShader(fs); return false; @@ -76,7 +82,7 @@ bool Shader::loadFromSource(const std::string& vsSource, const std::string& fsSo glDeleteShader(vs); glDeleteShader(fs); - E2D_LOG_DEBUG("Shader program created successfully"); + E2D_LOG_INFO("Shader program created successfully, program ID: {}", program_); return true; } @@ -131,6 +137,19 @@ void Shader::setMat4(const std::string& name, const float* value) { GLint location = getUniformLocation(name); if (location != -1) { glUniformMatrix4fv(location, 1, GL_FALSE, value); + // 调试:输出设置成功的uniform + static int logCount = 0; + if (logCount < 3) { + E2D_LOG_INFO("Set uniform '{}' at location {}", name, location); + logCount++; + } + } else { + // 调试:输出未找到的uniform + static bool loggedOnce = false; + if (!loggedOnce) { + E2D_LOG_WARN("Uniform '{}' not found in shader", name); + loggedOnce = true; + } } } diff --git a/src/renderer/uniform_buffer.cpp b/src/renderer/uniform_buffer.cpp index d10c923..7463d86 100644 --- a/src/renderer/uniform_buffer.cpp +++ b/src/renderer/uniform_buffer.cpp @@ -25,6 +25,7 @@ bool UniformBuffer::create(uint32_t size, uint32_t binding) { glBindBuffer(GL_UNIFORM_BUFFER, ubo_); glBufferData(GL_UNIFORM_BUFFER, size, nullptr, GL_DYNAMIC_DRAW); + glBindBufferBase(GL_UNIFORM_BUFFER, binding, ubo_); glBindBuffer(GL_UNIFORM_BUFFER, 0); E2D_LOG_DEBUG("UBO created: size={}, binding={}", size, binding); @@ -142,6 +143,7 @@ void UniformBufferManager::resetMaterialUBOs() { void UniformBufferManager::updateGlobalUBO(const void* data, uint32_t size) { if (globalUBO_) { globalUBO_->update(data, size); + globalUBO_->bind(GLOBAL_UBO_BINDING); } } diff --git a/src/renderer/viewport_adapter.cpp b/src/renderer/viewport_adapter.cpp new file mode 100644 index 0000000..fd700e3 --- /dev/null +++ b/src/renderer/viewport_adapter.cpp @@ -0,0 +1,431 @@ +#include +#include +#include + +namespace extra2d { + +/** + * @brief 默认构造函数 + */ +ViewportAdapter::ViewportAdapter() = default; + +/** + * @brief 构造函数 + * @param logicWidth 逻辑宽度 + * @param logicHeight 逻辑高度 + */ +ViewportAdapter::ViewportAdapter(float logicWidth, float logicHeight) { + config_.logicWidth = logicWidth; + config_.logicHeight = logicHeight; +} + +/** + * @brief 设置视口配置 + * @param config 视口配置 + */ +void ViewportAdapter::setConfig(const ViewportConfig &config) { + config_ = config; + matrixDirty_ = true; +} + +/** + * @brief 设置逻辑尺寸 + * @param width 逻辑宽度 + * @param height 逻辑高度 + */ +void ViewportAdapter::setLogicSize(float width, float height) { + config_.logicWidth = width; + config_.logicHeight = height; + matrixDirty_ = true; +} + +/** + * @brief 设置视口模式 + * @param mode 适配模式 + */ +void ViewportAdapter::setMode(ViewportMode mode) { + config_.mode = mode; + matrixDirty_ = true; +} + +/** + * @brief 设置黑边位置 + * @param position 黑边位置 + */ +void ViewportAdapter::setLetterboxPosition(LetterboxPosition position) { + config_.letterboxPosition = position; + matrixDirty_ = true; +} + +/** + * @brief 设置黑边颜色 + * @param color 黑边颜色 + */ +void ViewportAdapter::setLetterboxColor(const Color &color) { + config_.letterboxColor = color; +} + +/** + * @brief 更新视口适配计算 + * @param screenWidth 屏幕宽度 + * @param screenHeight 屏幕高度 + */ +void ViewportAdapter::update(int screenWidth, int screenHeight) { + if (screenWidth_ == screenWidth && screenHeight_ == screenHeight && + !matrixDirty_) { + return; + } + + screenWidth_ = screenWidth; + screenHeight_ = screenHeight; + matrixDirty_ = true; + + result_.hasLetterbox = false; + result_.letterbox.top = Rect::Zero; + result_.letterbox.bottom = Rect::Zero; + result_.letterbox.left = Rect::Zero; + result_.letterbox.right = Rect::Zero; + + switch (config_.mode) { + case ViewportMode::AspectRatio: + calculateAspectRatio(); + break; + case ViewportMode::Stretch: + calculateStretch(); + break; + case ViewportMode::Center: + calculateCenter(); + break; + case ViewportMode::Custom: + calculateCustom(); + break; + } +} + +/** + * @brief 计算宽高比适配模式 + * + * 保持逻辑宽高比,根据屏幕尺寸计算缩放和偏移 + */ +void ViewportAdapter::calculateAspectRatio() { + if (config_.logicHeight <= 0.0f || screenHeight_ <= 0) { + result_ = ViewportResult(); + return; + } + + float logicAspect = config_.logicWidth / config_.logicHeight; + float screenAspect = + static_cast(screenWidth_) / static_cast(screenHeight_); + + if (screenAspect > logicAspect) { + // 屏幕更宽,以高度为基准 + result_.uniformScale = + static_cast(screenHeight_) / config_.logicHeight; + result_.scaleX = result_.uniformScale; + result_.scaleY = result_.uniformScale; + result_.viewport.w = config_.logicWidth * result_.uniformScale; + result_.viewport.h = static_cast(screenHeight_); + result_.offset.x = (screenWidth_ - result_.viewport.w) / 2.0f; + result_.offset.y = 0.0f; + } else { + // 屏幕更高,以宽度为基准 + result_.uniformScale = + static_cast(screenWidth_) / config_.logicWidth; + result_.scaleX = result_.uniformScale; + result_.scaleY = result_.uniformScale; + result_.viewport.w = static_cast(screenWidth_); + result_.viewport.h = config_.logicHeight * result_.uniformScale; + result_.offset.x = 0.0f; + result_.offset.y = (screenHeight_ - result_.viewport.h) / 2.0f; + } + + result_.viewport.x = result_.offset.x; + result_.viewport.y = result_.offset.y; + + applyLetterboxPosition(static_cast(screenWidth_) - result_.viewport.w, + static_cast(screenHeight_) - + result_.viewport.h); + + calculateLetterbox(); +} + +/** + * @brief 计算拉伸适配模式 + * + * 拉伸逻辑视口以填满整个屏幕 + */ +void ViewportAdapter::calculateStretch() { + result_.scaleX = static_cast(screenWidth_) / config_.logicWidth; + result_.scaleY = static_cast(screenHeight_) / config_.logicHeight; + result_.uniformScale = std::min(result_.scaleX, result_.scaleY); + + result_.viewport.x = 0.0f; + result_.viewport.y = 0.0f; + result_.viewport.w = static_cast(screenWidth_); + result_.viewport.h = static_cast(screenHeight_); + + result_.offset = Vec2::Zero; + result_.hasLetterbox = false; +} + +/** + * @brief 计算居中适配模式 + * + * 将逻辑视口居中显示,可选自动缩放 + */ +void ViewportAdapter::calculateCenter() { + float displayWidth = config_.logicWidth; + float displayHeight = config_.logicHeight; + + if (config_.autoScaleInCenterMode) { + float scaleX = static_cast(screenWidth_) / config_.logicWidth; + float scaleY = static_cast(screenHeight_) / config_.logicHeight; + result_.uniformScale = std::min(scaleX, scaleY); + + if (result_.uniformScale < 1.0f) { + displayWidth = config_.logicWidth * result_.uniformScale; + displayHeight = config_.logicHeight * result_.uniformScale; + } else { + result_.uniformScale = 1.0f; + } + + result_.scaleX = result_.uniformScale; + result_.scaleY = result_.uniformScale; + } else { + result_.scaleX = 1.0f; + result_.scaleY = 1.0f; + result_.uniformScale = 1.0f; + } + + result_.offset.x = (screenWidth_ - displayWidth) / 2.0f; + result_.offset.y = (screenHeight_ - displayHeight) / 2.0f; + + result_.viewport.x = result_.offset.x; + result_.viewport.y = result_.offset.y; + result_.viewport.w = displayWidth; + result_.viewport.h = displayHeight; + + applyLetterboxPosition(static_cast(screenWidth_) - displayWidth, + static_cast(screenHeight_) - displayHeight); + + calculateLetterbox(); +} + +/** + * @brief 计算自定义适配模式 + * + * 使用自定义缩放和偏移参数 + */ +void ViewportAdapter::calculateCustom() { + result_.scaleX = config_.customScale; + result_.scaleY = config_.customScale; + result_.uniformScale = config_.customScale; + + if (config_.customViewport.empty()) { + float displayWidth = config_.logicWidth * config_.customScale; + float displayHeight = config_.logicHeight * config_.customScale; + + result_.offset = config_.customOffset; + result_.viewport.x = result_.offset.x; + result_.viewport.y = result_.offset.y; + result_.viewport.w = displayWidth; + result_.viewport.h = displayHeight; + } else { + result_.viewport = config_.customViewport; + result_.offset.x = config_.customViewport.x; + result_.offset.y = config_.customViewport.y; + } + + calculateLetterbox(); +} + +/** + * @brief 计算黑边区域 + * + * 根据视口偏移计算上下左右黑边矩形 + */ +void ViewportAdapter::calculateLetterbox() { + result_.hasLetterbox = false; + + float screenW = static_cast(screenWidth_); + float screenH = static_cast(screenHeight_); + + if (result_.offset.y > 0.0f) { + result_.hasLetterbox = true; + result_.letterbox.top = Rect(0.0f, 0.0f, screenW, result_.offset.y); + result_.letterbox.bottom = Rect(0.0f, result_.offset.y + result_.viewport.h, + screenW, result_.offset.y); + } + + if (result_.offset.x > 0.0f) { + result_.hasLetterbox = true; + result_.letterbox.left = Rect(0.0f, 0.0f, result_.offset.x, screenH); + result_.letterbox.right = Rect(result_.offset.x + result_.viewport.w, 0.0f, + result_.offset.x, screenH); + } +} + +/** + * @brief 根据黑边位置调整偏移 + * @param extraWidth 额外宽度 + * @param extraHeight 额外高度 + */ +void ViewportAdapter::applyLetterboxPosition(float extraWidth, + float extraHeight) { + if (extraWidth <= 0.0f && extraHeight <= 0.0f) { + return; + } + + switch (config_.letterboxPosition) { + case LetterboxPosition::Center: + break; + + case LetterboxPosition::LeftTop: + if (extraWidth > 0.0f) { + result_.offset.x = 0.0f; + } + if (extraHeight > 0.0f) { + result_.offset.y = 0.0f; + } + break; + + case LetterboxPosition::RightTop: + if (extraWidth > 0.0f) { + result_.offset.x = extraWidth; + } + if (extraHeight > 0.0f) { + result_.offset.y = 0.0f; + } + break; + + case LetterboxPosition::LeftBottom: + if (extraWidth > 0.0f) { + result_.offset.x = 0.0f; + } + if (extraHeight > 0.0f) { + result_.offset.y = extraHeight; + } + break; + + case LetterboxPosition::RightBottom: + if (extraWidth > 0.0f) { + result_.offset.x = extraWidth; + } + if (extraHeight > 0.0f) { + result_.offset.y = extraHeight; + } + break; + } + + result_.viewport.x = result_.offset.x; + result_.viewport.y = result_.offset.y; +} + +/** + * @brief 将屏幕坐标转换为逻辑坐标 + * @param screenPos 屏幕坐标 + * @return 逻辑坐标 + */ +Vec2 ViewportAdapter::screenToLogic(const Vec2 &screenPos) const { + return Vec2((screenPos.x - result_.offset.x) / result_.scaleX, + (screenPos.y - result_.offset.y) / result_.scaleY); +} + +/** + * @brief 将逻辑坐标转换为屏幕坐标 + * @param logicPos 逻辑坐标 + * @return 屏幕坐标 + */ +Vec2 ViewportAdapter::logicToScreen(const Vec2 &logicPos) const { + return Vec2(logicPos.x * result_.scaleX + result_.offset.x, + logicPos.y * result_.scaleY + result_.offset.y); +} + +/** + * @brief 将屏幕坐标转换为逻辑坐标 + * @param x 屏幕X坐标 + * @param y 屏幕Y坐标 + * @return 逻辑坐标 + */ +Vec2 ViewportAdapter::screenToLogic(float x, float y) const { + return screenToLogic(Vec2(x, y)); +} + +/** + * @brief 将逻辑坐标转换为屏幕坐标 + * @param x 逻辑X坐标 + * @param y 逻辑Y坐标 + * @return 屏幕坐标 + */ +Vec2 ViewportAdapter::logicToScreen(float x, float y) const { + return logicToScreen(Vec2(x, y)); +} + +/** + * @brief 获取视口变换矩阵 + * @return 视口变换矩阵 + */ +glm::mat4 ViewportAdapter::getMatrix() const { + if (matrixDirty_) { + viewportMatrix_ = glm::mat4(1.0f); + viewportMatrix_ = glm::translate( + viewportMatrix_, glm::vec3(result_.offset.x, result_.offset.y, 0.0f)); + viewportMatrix_ = glm::scale( + viewportMatrix_, glm::vec3(result_.scaleX, result_.scaleY, 1.0f)); + matrixDirty_ = false; + } + return viewportMatrix_; +} + +/** + * @brief 获取反向视口变换矩阵 + * @return 反向视口变换矩阵 + */ +glm::mat4 ViewportAdapter::getInvMatrix() const { + if (matrixDirty_) { + getMatrix(); + } + inverseViewportMatrix_ = glm::inverse(viewportMatrix_); + return inverseViewportMatrix_; +} + +/** + * @brief 检查屏幕坐标是否在视口内 + * @param screenPos 屏幕坐标 + * @return 如果在视口内返回 true + */ +bool ViewportAdapter::isInViewport(const Vec2 &screenPos) const { + return result_.viewport.contains(screenPos); +} + +/** + * @brief 检查屏幕坐标是否在黑边区域 + * @param screenPos 屏幕坐标 + * @return 如果在黑边区域返回 true + */ +bool ViewportAdapter::isInLetterbox(const Vec2 &screenPos) const { + if (!result_.hasLetterbox) { + return false; + } + + if (!result_.letterbox.top.empty() && + result_.letterbox.top.contains(screenPos)) { + return true; + } + if (!result_.letterbox.bottom.empty() && + result_.letterbox.bottom.contains(screenPos)) { + return true; + } + if (!result_.letterbox.left.empty() && + result_.letterbox.left.contains(screenPos)) { + return true; + } + if (!result_.letterbox.right.empty() && + result_.letterbox.right.contains(screenPos)) { + return true; + } + + return false; +} + +} // namespace extra2d diff --git a/src/scene/component.cpp b/src/scene/component.cpp new file mode 100644 index 0000000..d8637a2 --- /dev/null +++ b/src/scene/component.cpp @@ -0,0 +1,42 @@ +#include +#include + +namespace extra2d { + +void Component::onAttach(Node* owner) { + owner_ = owner; +} + +void Component::onDetach() { + owner_ = nullptr; +} + +void Component::onEnable() { + // 子类可重写 +} + +void Component::onDisable() { + // 子类可重写 +} + +void Component::update(float dt) { + // 子类可重写 + (void)dt; +} + +void Component::render() { + // 子类可重写 +} + +void Component::setEnabled(bool enabled) { + if (enabled_ != enabled) { + enabled_ = enabled; + if (enabled_) { + onEnable(); + } else { + onDisable(); + } + } +} + +} // namespace extra2d diff --git a/src/scene/components/camera_component.cpp b/src/scene/components/camera_component.cpp new file mode 100644 index 0000000..8f1bfba --- /dev/null +++ b/src/scene/components/camera_component.cpp @@ -0,0 +1,313 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 默认构造函数 + * + * 创建一个默认的正交相机,视口范围为 (0, 0) 到 (800, 600) + */ +CameraComponent::CameraComponent() { setOrtho(0, 800, 600, 0, -1, 1); } + +/** + * @brief 设置投影类型 + * @param type 投影类型 + */ +void CameraComponent::setProjectionType(ProjectionType type) { + projType_ = type; + markProjDirty(); +} + +/** + * @brief 设置正交投影 + * @param left 左边界 + * @param right 右边界 + * @param bottom 下边界 + * @param top 上边界 + * @param near 近裁剪面 + * @param far 远裁剪面 + * + * 对于2D游戏,Y轴向下增长(屏幕坐标系) + * 传入 (bottom > top) 实现Y轴向下 + */ +void CameraComponent::setOrtho(float left, float right, float bottom, float top, + float near, float far) { + projType_ = ProjectionType::Orthographic; + left_ = left; + right_ = right; + bottom_ = bottom; + top_ = top; + near_ = near; + far_ = far; + markProjDirty(); + + E2D_LOG_DEBUG("Ortho projection set: L={}, R={}, B={}, T={}, N={}, F={}", + left, right, bottom, top, near, far); +} + +/** + * @brief 设置透视投影 + * @param fov 视场角(度) + * @param aspect 宽高比 + * @param near 近裁剪面 + * @param far 远裁剪面 + */ +void CameraComponent::setPerspective(float fov, float aspect, float near, + float far) { + projType_ = ProjectionType::Perspective; + fov_ = fov; + aspect_ = aspect; + near_ = near; + far_ = far; + markProjDirty(); +} + +/** + * @brief 设置缩放级别 + * @param zoom 缩放值(1.0为正常大小) + */ +void CameraComponent::setZoom(float zoom) { + zoom_ = std::max(0.001f, zoom); + markViewDirty(); + markProjDirty(); +} + +/** + * @brief 设置旋转角度 + * @param degrees 旋转角度(度数) + */ +void CameraComponent::setRotation(float degrees) { + rotation_ = degrees; + markViewDirty(); +} + +/** + * @brief 更新视图矩阵(延迟计算) + * + * 变换顺序:平移 -> 旋转 -> 缩放(逆序应用) + * View = T(-position) * R(-rotation) * S(1/zoom) + */ +void CameraComponent::updateViewMatrix() const { + if (!viewDirty_) + return; + + viewMatrix_ = Mat4(1.0f); + + // 获取相机位置(从节点获取) + Vec2 position; + if (owner_) { + position = owner_->getPosition(); + } + + // 1. 平移(最后应用):将相机位置移到原点 + viewMatrix_ = + glm::translate(viewMatrix_, glm::vec3(-position.x, -position.y, 0.0f)); + + // 2. 旋转(中间应用) + if (rotation_ != 0.0f) { + constexpr float DEG_TO_RAD = 3.14159265358979323846f / 180.0f; + viewMatrix_ = glm::rotate(viewMatrix_, -rotation_ * DEG_TO_RAD, + glm::vec3(0.0f, 0.0f, 1.0f)); + } + + // 3. 缩放(最先应用) + if (zoom_ != 1.0f) { + viewMatrix_ = + glm::scale(viewMatrix_, glm::vec3(1.0f / zoom_, 1.0f / zoom_, 1.0f)); + } + + viewDirty_ = false; +} + +/** + * @brief 更新投影矩阵(延迟计算) + * + * 对于2D游戏,Y轴向下增长(屏幕坐标系) + * 使用 glm::ortho 简化实现 + */ +void CameraComponent::updateProjectionMatrix() const { + if (!projDirty_) + return; + + if (projType_ == ProjectionType::Orthographic) { + // 2D正交投影:直接使用 setOrtho 设置的参数 + // Y轴向下:传入 (bottom > top) + projMatrix_ = glm::ortho(left_, right_, bottom_, top_, near_, far_); + } else { + // 透视投影 + float fovRad = glm::radians(fov_); + projMatrix_ = glm::perspective(fovRad, aspect_, near_, far_); + } + + projDirty_ = false; +} + +/** + * @brief 获取视图矩阵 + * @return 视图矩阵 + */ +Mat4 CameraComponent::getViewMatrix() const { + updateViewMatrix(); + return viewMatrix_; +} + +/** + * @brief 获取投影矩阵 + * @return 投影矩阵 + */ +Mat4 CameraComponent::getProjectionMatrix() const { + updateProjectionMatrix(); + return projMatrix_; +} + +/** + * @brief 获取视图投影矩阵 + * @return 视图投影矩阵 + */ +Mat4 CameraComponent::getViewProjectionMatrix() const { + if (vpDirty_) { + vpMatrix_ = getProjectionMatrix() * getViewMatrix(); + vpDirty_ = false; + } + return vpMatrix_; +} + +/** + * @brief 将屏幕坐标转换为世界坐标 + * @param screenPos 屏幕坐标 + * @return 世界坐标 + */ +Vec2 CameraComponent::screenToWorld(const Vec2 &screenPos) const { + // 使用逆视图投影矩阵转换 + Mat4 invVP = glm::inverse(getViewProjectionMatrix()); + glm::vec4 ndc(screenPos.x, screenPos.y, 0.0f, 1.0f); + glm::vec4 world = invVP * ndc; + return Vec2(world.x, world.y); +} + +/** + * @brief 将屏幕坐标转换为世界坐标 + * @param x 屏幕X坐标 + * @param y 屏幕Y坐标 + * @return 世界坐标 + */ +Vec2 CameraComponent::screenToWorld(float x, float y) const { + return screenToWorld(Vec2(x, y)); +} + +/** + * @brief 将世界坐标转换为屏幕坐标 + * @param worldPos 世界坐标 + * @return 屏幕坐标 + */ +Vec2 CameraComponent::worldToScreen(const Vec2 &worldPos) const { + glm::vec4 world(worldPos.x, worldPos.y, 0.0f, 1.0f); + glm::vec4 screen = getViewProjectionMatrix() * world; + return Vec2(screen.x, screen.y); +} + +/** + * @brief 将世界坐标转换为屏幕坐标 + * @param x 世界X坐标 + * @param y 世界Y坐标 + * @return 屏幕坐标 + */ +Vec2 CameraComponent::worldToScreen(float x, float y) const { + return worldToScreen(Vec2(x, y)); +} + +/** + * @brief 设置视口 + * @param viewport 视口矩形 + */ +void CameraComponent::setViewport(const Rect &viewport) { + viewport_ = viewport; +} + +/** + * @brief 设置相机边界限制 + * @param bounds 边界矩形 + */ +void CameraComponent::setBounds(const Rect &bounds) { + bounds_ = bounds; + hasBounds_ = true; +} + +/** + * @brief 清除相机边界限制 + */ +void CameraComponent::clearBounds() { hasBounds_ = false; } + +/** + * @brief 将相机位置限制在边界内 + */ +void CameraComponent::clampToBounds() { + if (!hasBounds_ || !owner_) + return; + + float viewportWidth = (right_ - left_) / zoom_; + float viewportHeight = (bottom_ - top_) / zoom_; + + float minX = bounds_.x + viewportWidth * 0.5f; + float maxX = bounds_.x + bounds_.w - viewportWidth * 0.5f; + float minY = bounds_.y + viewportHeight * 0.5f; + float maxY = bounds_.y + bounds_.h - viewportHeight * 0.5f; + + Vec2 pos = owner_->getPosition(); + + if (minX > maxX) { + pos.x = bounds_.x + bounds_.w * 0.5f; + } else { + pos.x = std::clamp(pos.x, minX, maxX); + } + + if (minY > maxY) { + pos.y = bounds_.y + bounds_.h * 0.5f; + } else { + pos.y = std::clamp(pos.y, minY, maxY); + } + + owner_->setPosition(pos); + markViewDirty(); +} + +/** + * @brief 移动相机位置 + * @param offset 位置偏移量 + */ +void CameraComponent::move(const Vec2 &offset) { + if (!owner_) + return; + + Vec2 pos = owner_->getPosition(); + pos += offset; + owner_->setPosition(pos); + markViewDirty(); +} + +/** + * @brief 移动相机位置 + * @param x X方向偏移量 + * @param y Y方向偏移量 + */ +void CameraComponent::move(float x, float y) { move(Vec2(x, y)); } + +/** + * @brief 将相机移动到目标位置 + * @param target 目标位置 + */ +void CameraComponent::lookAt(const Vec2 &target) { + if (!owner_) + return; + + owner_->setPosition(target); + markViewDirty(); +} + +} // namespace extra2d diff --git a/src/scene/components/sprite_renderer.cpp b/src/scene/components/sprite_renderer.cpp new file mode 100644 index 0000000..4ba67f6 --- /dev/null +++ b/src/scene/components/sprite_renderer.cpp @@ -0,0 +1,52 @@ +#include +#include +#include +#include + +namespace extra2d { + +SpriteRenderer::SpriteRenderer() { + // 默认材质和纹理为无效句柄 + // 渲染时会使用默认材质 +} + +void SpriteRenderer::onAttach(Node *owner) { Component::onAttach(owner); } + +void SpriteRenderer::setMaterial(MaterialHandle material) { + material_ = material; +} + +void SpriteRenderer::setTexture(TextureHandle texture) { texture_ = texture; } + +void SpriteRenderer::setColor(const Color &color) { color_ = color; } + +void SpriteRenderer::render() { + if (!isEnabled() || !owner_ || !owner_->isVisible()) { + return; + } + + // 获取世界变换(已包含锚点计算) + Transform worldTransform = owner_->getWorldTransform(); + + // 构建渲染命令 + RenderCommand cmd; + cmd.type = RenderCommandType::DrawMesh; + + // 计算排序键(简单实现:材质ID + 纹理ID) + uint32_t materialId = static_cast(material_ & 0xFFFFFFFF); + uint32_t textureId = static_cast(texture_ & 0xFFFFFFFF); + cmd.sortKey = (materialId << 16) | (textureId & 0xFFFF); + + // 设置网格(使用默认四边形) + cmd.drawMesh.mesh = INVALID_MESH_HANDLE; // 渲染器会使用默认四边形 + cmd.drawMesh.material = material_; + cmd.setTransform(worldTransform); + + // 设置顶点颜色 + cmd.setColor(color_); + + // 提交渲染命令 + events::OnRenderSubmit::emit(cmd); +} + +} // namespace extra2d diff --git a/src/scene/components/transform_component.cpp b/src/scene/components/transform_component.cpp new file mode 100644 index 0000000..ce13ae4 --- /dev/null +++ b/src/scene/components/transform_component.cpp @@ -0,0 +1,163 @@ +#include +#include +#include +#include + +namespace extra2d { + +TransformComponent::TransformComponent() { + local_.pos = Vec2(0, 0); + local_.scale = Vec2(1, 1); + local_.rot = 0; +} + +void TransformComponent::setPosition(const Vec2& pos) { + if (local_.pos != pos) { + local_.pos = pos; + setDirty(); + } +} + +void TransformComponent::setRotation(float rot) { + if (local_.rot != rot) { + local_.rot = rot; + setDirty(); + } +} + +void TransformComponent::setScale(const Vec2& scale) { + if (local_.scale != scale) { + local_.scale = scale; + setDirty(); + } +} + +void TransformComponent::setAnchor(const Vec2& anchor) { + // 限制锚点在 [0, 1] 范围内 + Vec2 clampedAnchor( + anchor.x < 0 ? 0 : (anchor.x > 1 ? 1 : anchor.x), + anchor.y < 0 ? 0 : (anchor.y > 1 ? 1 : anchor.y) + ); + if (anchor_ != clampedAnchor) { + anchor_ = clampedAnchor; + setDirty(); + } +} + +void TransformComponent::setAnchorPoint(float x, float y) { + setAnchor(Vec2(x, y)); +} + +void TransformComponent::setSize(const Vec2& size) { + if (size_ != size) { + size_ = size; + setDirty(); + } +} + +void TransformComponent::setSize(float width, float height) { + setSize(Vec2(width, height)); +} + +Vec2 TransformComponent::getAnchorOffset() const { + // 锚点偏移 = (锚点 - 0.5) * 尺寸 + // 这样 (0.5, 0.5) 锚点偏移为 0,(0, 0) 锚点偏移为 (-0.5 * w, -0.5 * h) + return Vec2( + (anchor_.x - 0.5f) * size_.x, + (anchor_.y - 0.5f) * size_.y + ); +} + +void TransformComponent::setDirty() { + dirty_ = true; + // 递归标记所有子节点为脏 + if (owner_) { + for (auto& child : owner_->getChildren()) { + if (auto* transform = child->getTransform()) { + transform->setDirty(); + } + } + } +} + +/** + * @brief 更新世界变换 + * + * 尺寸(size)用于计算锚点偏移和缩放。 + * 默认1x1的四边形配合64x64的尺寸会正确显示为64x64像素大小。 + */ +void TransformComponent::updateWorldTransform() const { + if (!dirty_) return; + + // 计算本地旋转(度数转弧度) + float rotRad = local_.rot * glm::pi() / 180.0f; + float c = std::cos(rotRad); + float s = std::sin(rotRad); + + // 获取锚点偏移(尺寸已包含在计算中) + Vec2 anchorOffset = getAnchorOffset(); + + // 计算世界变换 + if (owner_ && owner_->getParent()) { + // 有父节点,需要组合变换 + Transform parentWorld = owner_->getParent()->getWorldTransform(); + + // 本地旋转后的锚点偏移 + float rotatedOffsetX = anchorOffset.x * c - anchorOffset.y * s; + float rotatedOffsetY = anchorOffset.x * s + anchorOffset.y * c; + + // 组合变换 + // 1. 先应用父节点的缩放到锚点偏移 + float finalOffsetX = rotatedOffsetX * parentWorld.scale.x; + float finalOffsetY = rotatedOffsetY * parentWorld.scale.y; + + // 2. 计算世界位置:父位置 + 旋转后的本地位置 + 锚点偏移 + float parentCos = std::cos(parentWorld.rot); + float parentSin = std::sin(parentWorld.rot); + float localRotatedX = local_.pos.x * parentCos - local_.pos.y * parentSin; + float localRotatedY = local_.pos.x * parentSin + local_.pos.y * parentCos; + + world_.pos.x = parentWorld.pos.x + localRotatedX * parentWorld.scale.x + finalOffsetX; + world_.pos.y = parentWorld.pos.y + localRotatedY * parentWorld.scale.y + finalOffsetY; + + // 世界旋转和缩放(包含尺寸) + world_.rot = parentWorld.rot + rotRad; + world_.scale.x = parentWorld.scale.x * local_.scale.x * size_.x; + world_.scale.y = parentWorld.scale.y * local_.scale.y * size_.y; + } else { + // 无父节点,本地变换即世界变换 + world_.pos.x = local_.pos.x + anchorOffset.x * c - anchorOffset.y * s; + world_.pos.y = local_.pos.y + anchorOffset.x * s + anchorOffset.y * c; + world_.rot = rotRad; + // 缩放包含尺寸 + world_.scale.x = local_.scale.x * size_.x; + world_.scale.y = local_.scale.y * size_.y; + } + + dirty_ = false; +} + +Transform TransformComponent::getWorldTransform() const { + updateWorldTransform(); + return world_; +} + +Mat4 TransformComponent::getWorldMatrix() const { + updateWorldTransform(); + + // 构建世界矩阵 + Mat4 matrix = Mat4(1.0f); + + // 平移 + matrix = glm::translate(matrix, glm::vec3(world_.pos.x, world_.pos.y, 0.0f)); + + // 旋转(Z轴) + matrix = glm::rotate(matrix, world_.rot, glm::vec3(0.0f, 0.0f, 1.0f)); + + // 缩放 + matrix = glm::scale(matrix, glm::vec3(world_.scale.x, world_.scale.y, 1.0f)); + + return matrix; +} + +} // namespace extra2d diff --git a/src/scene/director.cpp b/src/scene/director.cpp new file mode 100644 index 0000000..8ab5213 --- /dev/null +++ b/src/scene/director.cpp @@ -0,0 +1,154 @@ +#include +#include +#include +#include + +namespace extra2d { + +Director::Director() { +} + +Director::~Director() { + shutdown(); +} + +bool Director::init() { + return true; +} + +void Director::shutdown() { + // 结束当前场景 + if (runningScene_) { + runningScene_->onExit(); + runningScene_.reset(); + } + + // 清空场景栈 + while (!sceneStack_.empty()) { + sceneStack_.pop(); + } + + running_ = false; +} + +void Director::runScene(Ptr scene) { + if (!scene) return; + + // 结束当前场景 + if (runningScene_) { + runningScene_->onExit(); + } + + // 清空场景栈 + while (!sceneStack_.empty()) { + sceneStack_.pop(); + } + + // 运行新场景 + runningScene_ = scene; + runningScene_->onEnter(); + running_ = true; + needEnd_ = false; +} + +void Director::replaceScene(Ptr scene) { + if (!scene) return; + + // 结束当前场景 + if (runningScene_) { + runningScene_->onExit(); + } + + // 替换场景 + runningScene_ = scene; + runningScene_->onEnter(); +} + +void Director::pushScene(Ptr scene) { + if (!scene) return; + + // 暂停当前场景 + if (runningScene_) { + // 可以在这里添加暂停逻辑 + sceneStack_.push(runningScene_); + } + + // 运行新场景 + runningScene_ = scene; + runningScene_->onEnter(); +} + +void Director::popScene() { + if (sceneStack_.empty()) { + // 没有场景可以弹出,结束运行 + end(); + return; + } + + // 结束当前场景 + if (runningScene_) { + runningScene_->onExit(); + } + + // 弹出栈顶场景 + runningScene_ = sceneStack_.top(); + sceneStack_.pop(); + + // 恢复场景 + // 可以在这里添加恢复逻辑 +} + +void Director::end() { + needEnd_ = true; + running_ = false; + + if (runningScene_) { + runningScene_->onExit(); + runningScene_.reset(); + } +} + +Scene* Director::getRunningScene() const { + return runningScene_.get(); +} + +CameraComponent* Director::getMainCamera() const { + if (runningScene_) { + return runningScene_->getMainCamera(); + } + return nullptr; +} + +void Director::update(float dt) { + if (!running_ || !runningScene_) return; + + runningScene_->update(dt); +} + +void Director::render() { + if (!running_ || !runningScene_) return; + + // 从场景获取相机并上传矩阵 + CameraComponent* camera = runningScene_->getMainCamera(); + if (camera) { + Mat4 viewProj = camera->getViewProjectionMatrix(); + + // 调试输出:打印视图投影矩阵 + static bool logged = false; + if (!logged) { + const float* m = glm::value_ptr(viewProj); + E2D_LOG_INFO("ViewProjection Matrix:"); + E2D_LOG_INFO(" {:.4f} {:.4f} {:.4f} {:.4f}", m[0], m[4], m[8], m[12]); + E2D_LOG_INFO(" {:.4f} {:.4f} {:.4f} {:.4f}", m[1], m[5], m[9], m[13]); + E2D_LOG_INFO(" {:.4f} {:.4f} {:.4f} {:.4f}", m[2], m[6], m[10], m[14]); + E2D_LOG_INFO(" {:.4f} {:.4f} {:.4f} {:.4f}", m[3], m[7], m[11], m[15]); + logged = true; + } + + events::OnRenderSetCamera::emit(viewProj); + } + + runningScene_->render(); +} + +} // namespace extra2d diff --git a/src/scene/node.cpp b/src/scene/node.cpp new file mode 100644 index 0000000..6a07ebd --- /dev/null +++ b/src/scene/node.cpp @@ -0,0 +1,207 @@ +#include +#include + +namespace extra2d { + +Node::Node() { + // 自动添加 TransformComponent + auto transform = makePtr(); + transform_ = transform.get(); + components_.push_back(transform); + transform_->onAttach(this); +} + +Node::~Node() { + // 先分离所有组件 + for (auto& comp : components_) { + comp->onDetach(); + } + components_.clear(); + + // 移除所有子节点 + removeAllChildren(); + + // 从父节点移除 + if (parent_) { + parent_->removeChild(this); + } +} + +void Node::addChild(Ptr child) { + if (!child || child.get() == this) return; + + // 从原父节点移除 + if (child->parent_) { + child->parent_->removeChild(child.get()); + } + + child->parent_ = this; + children_.push_back(child); +} + +void Node::removeChild(Node* child) { + if (!child) return; + + for (auto it = children_.begin(); it != children_.end(); ++it) { + if (it->get() == child) { + child->parent_ = nullptr; + children_.erase(it); + return; + } + } +} + +void Node::removeFromParent() { + if (parent_) { + parent_->removeChild(this); + } +} + +void Node::removeAllChildren() { + for (auto& child : children_) { + child->parent_ = nullptr; + } + children_.clear(); +} + +// ======================================== +// 变换便捷接口 +// ======================================== + +void Node::setPosition(const Vec2& pos) { + if (transform_) { + transform_->setPosition(pos); + } +} + +void Node::setPosition(float x, float y) { + setPosition(Vec2(x, y)); +} + +Vec2 Node::getPosition() const { + return transform_ ? transform_->getPosition() : Vec2(0, 0); +} + +void Node::setRotation(float rot) { + if (transform_) { + transform_->setRotation(rot); + } +} + +float Node::getRotation() const { + return transform_ ? transform_->getRotation() : 0.0f; +} + +void Node::setScale(const Vec2& scale) { + if (transform_) { + transform_->setScale(scale); + } +} + +void Node::setScale(float scale) { + setScale(Vec2(scale, scale)); +} + +void Node::setScale(float x, float y) { + setScale(Vec2(x, y)); +} + +Vec2 Node::getScale() const { + return transform_ ? transform_->getScale() : Vec2(1, 1); +} + +void Node::setSize(const Vec2& size) { + if (transform_) { + transform_->setSize(size); + } +} + +void Node::setSize(float width, float height) { + setSize(Vec2(width, height)); +} + +Vec2 Node::getSize() const { + return transform_ ? transform_->getSize() : Vec2(100, 100); +} + +void Node::setAnchor(const Vec2& anchor) { + if (transform_) { + transform_->setAnchor(anchor); + } +} + +void Node::setAnchor(float x, float y) { + setAnchor(Vec2(x, y)); +} + +Vec2 Node::getAnchor() const { + return transform_ ? transform_->getAnchor() : Vec2(0.5f, 0.5f); +} + +Transform Node::getWorldTransform() const { + return transform_ ? transform_->getWorldTransform() : Transform(); +} + +Mat4 Node::getWorldMatrix() const { + return transform_ ? transform_->getWorldMatrix() : Mat4(); +} + +// ======================================== +// 生命周期 +// ======================================== + +void Node::onEnter() { + // 通知所有组件 + for (auto& comp : components_) { + // 组件没有 onEnter,可以在这里扩展 + } + + // 递归通知子节点 + for (auto& child : children_) { + child->onEnter(); + } +} + +void Node::onExit() { + // 递归通知子节点 + for (auto& child : children_) { + child->onExit(); + } + + // 通知所有组件 + for (auto& comp : components_) { + // 组件没有 onExit,可以在这里扩展 + } +} + +void Node::update(float dt) { + // 更新所有组件 + for (auto& comp : components_) { + if (comp->isEnabled()) { + comp->update(dt); + } + } + + // 递归更新子节点 + for (auto& child : children_) { + child->update(dt); + } +} + +void Node::render() { + if (!visible_) return; + + // 渲染所有组件 + for (auto& comp : components_) { + if (comp->isEnabled()) { + comp->render(); + } + } + + // 递归渲染子节点 + for (auto& child : children_) { + child->render(); + } +} + +} // namespace extra2d diff --git a/src/scene/scene.cpp b/src/scene/scene.cpp new file mode 100644 index 0000000..eb9b879 --- /dev/null +++ b/src/scene/scene.cpp @@ -0,0 +1,128 @@ +#include +#include +#include + +namespace extra2d { + +Scene::Scene() { +} + +Scene::~Scene() { + if (entered_) { + onExit(); + } + removeAllChildren(); +} + +void Scene::addChild(Ptr node) { + if (!node) return; + + node->removeFromParent(); + node->onEnter(); + rootNodes_.push_back(node); +} + +void Scene::removeChild(Node* node) { + if (!node) return; + + for (auto it = rootNodes_.begin(); it != rootNodes_.end(); ++it) { + if (it->get() == node) { + node->onExit(); + rootNodes_.erase(it); + return; + } + } +} + +void Scene::removeAllChildren() { + for (auto& node : rootNodes_) { + node->onExit(); + } + rootNodes_.clear(); +} + +Node* Scene::findNode(const std::string& name) { + // 广度优先搜索 + std::queue queue; + + for (auto& node : rootNodes_) { + queue.push(node.get()); + } + + while (!queue.empty()) { + Node* current = queue.front(); + queue.pop(); + + if (current->getName() == name) { + return current; + } + + for (auto& child : current->getChildren()) { + queue.push(child.get()); + } + } + + return nullptr; +} + +Node* Scene::findNodeByTag(int32 tag) { + // 广度优先搜索 + std::queue queue; + + for (auto& node : rootNodes_) { + queue.push(node.get()); + } + + while (!queue.empty()) { + Node* current = queue.front(); + queue.pop(); + + if (current->getTag() == tag) { + return current; + } + + for (auto& child : current->getChildren()) { + queue.push(child.get()); + } + } + + return nullptr; +} + +void Scene::setMainCamera(CameraComponent* camera) { + mainCamera_ = camera; +} + +void Scene::onEnter() { + entered_ = true; + + // 通知所有根节点 + for (auto& node : rootNodes_) { + node->onEnter(); + } +} + +void Scene::onExit() { + entered_ = false; + + // 通知所有根节点 + for (auto& node : rootNodes_) { + node->onExit(); + } +} + +void Scene::update(float dt) { + // 更新所有根节点 + for (auto& node : rootNodes_) { + node->update(dt); + } +} + +void Scene::render() { + // 渲染所有根节点 + for (auto& node : rootNodes_) { + node->render(); + } +} + +} // namespace extra2d diff --git a/src/scene/scene_module.cpp b/src/scene/scene_module.cpp new file mode 100644 index 0000000..4337ad8 --- /dev/null +++ b/src/scene/scene_module.cpp @@ -0,0 +1,56 @@ +#include + +namespace extra2d { + +SceneModule::SceneModule() = default; + +SceneModule::~SceneModule() { + shutdown(); +} + +SceneModule::SceneModule(SceneModule &&) noexcept = default; + +SceneModule &SceneModule::operator=(SceneModule &&) noexcept = default; + +bool SceneModule::init() { + // 创建导演 + director_ = makePtr(); + + if (!director_->init()) { + return false; + } + + // 绑定事件 + onUpdateListener_.bind([this](float dt) { + if (director_) { + director_->update(dt); + } + }); + + onRenderBeginListener_.bind([this]() { + // 渲染开始,触发场景渲染 + if (director_) { + director_->render(); + } + }); + + onRenderEndListener_.bind([]() { + // 渲染结束,清理工作 + }); + + return true; +} + +void SceneModule::shutdown() { + if (director_) { + director_->shutdown(); + director_.reset(); + } + + // 重置监听器(会自动注销) + onUpdateListener_.reset(); + onRenderBeginListener_.reset(); + onRenderEndListener_.reset(); +} + +} // namespace extra2d diff --git a/src/types/math/math_types.cpp b/src/types/math/math_types.cpp new file mode 100644 index 0000000..93923ee --- /dev/null +++ b/src/types/math/math_types.cpp @@ -0,0 +1,15 @@ +#include +#include + +namespace extra2d { + +// Vec2 静态成员定义 +const Vec2 Vec2::Zero = Vec2(0.0f, 0.0f); +const Vec2 Vec2::One = Vec2(1.0f, 1.0f); +const Vec2 Vec2::UnitX = Vec2(1.0f, 0.0f); +const Vec2 Vec2::UnitY = Vec2(0.0f, 1.0f); + +// Rect 静态成员定义 +const Rect Rect::Zero = Rect(0.0f, 0.0f, 0.0f, 0.0f); + +} // namespace extra2d diff --git a/xmake.lua b/xmake.lua index a4e19a6..829321b 100644 --- a/xmake.lua +++ b/xmake.lua @@ -94,6 +94,7 @@ define_extra2d_engine() -- 示例程序目标(作为子项目) if is_config("examples","true") then includes("examples/hello_world", {rootdir = "examples/hello_world"}) + includes("examples/scene_graph_demo", {rootdir = "examples/scene_graph_demo"}) end