feat(场景图系统): 实现完整的场景图模块和示例程序
新增场景图系统核心组件: - Director 场景管理 - Scene 场景容器 - Node 节点层级 - Component 组件系统 - Transform 变换(含锚点) - Camera 相机 - SpriteRenderer 精灵渲染 添加场景图示例程序,演示: - 节点层级和变换继承 - 组件系统使用 - 相机设置 - 精灵渲染 同时优化了渲染系统: - 修改渲染命令结构 - 添加视口适配器 - 改进着色器错误处理 - 增强材质系统功能
This commit is contained in:
parent
b4be0d84f8
commit
92be7d9d18
|
|
@ -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<GameScene>();
|
||||||
|
director->runScene(scene);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 创建节点
|
||||||
|
```cpp
|
||||||
|
auto node = makePtr<Node>();
|
||||||
|
node->setPosition(100, 200);
|
||||||
|
node->setAnchor(0.5f, 0.5f); // 中心锚点
|
||||||
|
node->setSize(64, 64);
|
||||||
|
scene->addChild(node);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 添加组件
|
||||||
|
```cpp
|
||||||
|
auto sprite = makePtr<SpriteRenderer>();
|
||||||
|
sprite->setColor(Color::Red);
|
||||||
|
node->addComponent(sprite);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 设置相机
|
||||||
|
```cpp
|
||||||
|
auto camera = makePtr<CameraComponent>();
|
||||||
|
camera->setOrtho(0, 1280, 0, 720, -1, 1);
|
||||||
|
cameraNode->addComponent(camera);
|
||||||
|
scene->setMainCamera(camera);
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
#include "game_scene.h"
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// PlayerNode 实现
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
PlayerNode::PlayerNode() {
|
||||||
|
setName("Player");
|
||||||
|
setTag(1);
|
||||||
|
|
||||||
|
// 设置玩家尺寸
|
||||||
|
setSize(64.0f, 64.0f);
|
||||||
|
|
||||||
|
// 设置锚点为中心点
|
||||||
|
setAnchor(0.5f, 0.5f);
|
||||||
|
|
||||||
|
// 添加精灵渲染组件
|
||||||
|
auto sprite = makePtr<SpriteRenderer>();
|
||||||
|
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<SpriteRenderer>();
|
||||||
|
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<PlayerNode>();
|
||||||
|
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<RotatingDecoration>();
|
||||||
|
|
||||||
|
// 计算位置(圆形分布)
|
||||||
|
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<SpriteRenderer>();
|
||||||
|
if (sprite) {
|
||||||
|
float hue = static_cast<float>(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<Node>();
|
||||||
|
cameraNode->setName("MainCamera");
|
||||||
|
// 相机位置在(0, 0),这样世界坐标直接映射到屏幕
|
||||||
|
cameraNode->setPosition(0.0f, 0.0f);
|
||||||
|
|
||||||
|
// 添加相机组件
|
||||||
|
auto camera = makePtr<CameraComponent>();
|
||||||
|
// 使用标准的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);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d.h>
|
||||||
|
|
||||||
|
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<PlayerNode> player_;
|
||||||
|
std::vector<Ptr<RotatingDecoration>> decorations_;
|
||||||
|
float sceneTime_ = 0.0f;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
/**
|
||||||
|
* @file main.cpp
|
||||||
|
* @brief 场景图系统示例程序
|
||||||
|
*
|
||||||
|
* 演示 Extra2D 场景图模块的核心功能:
|
||||||
|
* - Director 场景管理
|
||||||
|
* - Scene 场景容器
|
||||||
|
* - Node 节点层级
|
||||||
|
* - Component 组件系统
|
||||||
|
* - Transform 变换(含锚点)
|
||||||
|
* - Camera 相机
|
||||||
|
* - SpriteRenderer 精灵渲染
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <extra2d.h>
|
||||||
|
#include "game_scene.h"
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
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<SceneModule>();
|
||||||
|
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<GameScene>();
|
||||||
|
director->runScene(gameScene);
|
||||||
|
|
||||||
|
printf("Game scene started\n");
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// 4. 运行应用主循环
|
||||||
|
// ========================================
|
||||||
|
app->run();
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// 5. 清理(自动进行)
|
||||||
|
// ========================================
|
||||||
|
printf("Application shutting down...\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include <event/event_bus_macros.h>
|
#include <event/event_bus_macros.h>
|
||||||
#include <renderer/render_types.h>
|
#include <renderer/render_types.h>
|
||||||
#include <types/base/types.h>
|
#include <types/base/types.h>
|
||||||
|
#include <types/math/mat4.h>
|
||||||
|
|
||||||
namespace extra2d::events {
|
namespace extra2d::events {
|
||||||
|
|
||||||
|
|
@ -243,6 +244,14 @@ DECLARE_EVENT_0(OnRenderBegin, Engine)
|
||||||
*/
|
*/
|
||||||
DECLARE_EVENT_1(OnRenderSubmit, Engine, extra2d::RenderCommand)
|
DECLARE_EVENT_1(OnRenderSubmit, Engine, extra2d::RenderCommand)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染设置相机事件
|
||||||
|
*
|
||||||
|
* 场景图模块通过此事件设置相机矩阵
|
||||||
|
* @param viewProjection 视图投影矩阵
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_1(OnRenderSetCamera, Engine, extra2d::Mat4)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 渲染结束事件
|
* @brief 渲染结束事件
|
||||||
*
|
*
|
||||||
|
|
@ -250,4 +259,11 @@ DECLARE_EVENT_1(OnRenderSubmit, Engine, extra2d::RenderCommand)
|
||||||
*/
|
*/
|
||||||
DECLARE_EVENT_0(OnRenderEnd, Engine)
|
DECLARE_EVENT_0(OnRenderEnd, Engine)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染呈现事件
|
||||||
|
*
|
||||||
|
* 在渲染完成并准备好呈现时触发,窗口模块应调用 swapBuffers
|
||||||
|
*/
|
||||||
|
DECLARE_EVENT_0(OnRenderPresent, Engine)
|
||||||
|
|
||||||
} // namespace extra2d::events
|
} // namespace extra2d::events
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
#include <types/math/transform.h>
|
#include <types/math/transform.h>
|
||||||
#include <types/math/vec2.h>
|
#include <types/math/vec2.h>
|
||||||
#include <types/math/vec3.h>
|
#include <types/math/vec3.h>
|
||||||
|
#include <types/math/mat4.h>
|
||||||
|
|
||||||
// Context (核心上下文)
|
// Context (核心上下文)
|
||||||
#include <context/context.h>
|
#include <context/context.h>
|
||||||
|
|
@ -49,6 +50,16 @@
|
||||||
#include <renderer/mesh.h>
|
#include <renderer/mesh.h>
|
||||||
#include <renderer/uniform_buffer.h>
|
#include <renderer/uniform_buffer.h>
|
||||||
|
|
||||||
|
// Scene Graph System
|
||||||
|
#include <scene/scene_module.h>
|
||||||
|
#include <scene/director.h>
|
||||||
|
#include <scene/scene.h>
|
||||||
|
#include <scene/node.h>
|
||||||
|
#include <scene/component.h>
|
||||||
|
#include <scene/components/transform_component.h>
|
||||||
|
#include <scene/components/sprite_renderer.h>
|
||||||
|
#include <scene/components/camera_component.h>
|
||||||
|
|
||||||
// Application
|
// Application
|
||||||
#include <app/application.h>
|
#include <app/application.h>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,13 @@ private:
|
||||||
*/
|
*/
|
||||||
void onModuleConfig(const AppConfig &config);
|
void onModuleConfig(const AppConfig &config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 监听渲染呈现事件
|
||||||
|
*
|
||||||
|
* 当渲染完成时调用 swapBuffers 呈现渲染结果
|
||||||
|
*/
|
||||||
|
void onRenderPresent();
|
||||||
|
|
||||||
SDL_Window *window_ = nullptr;
|
SDL_Window *window_ = nullptr;
|
||||||
SDL_GLContext glContext_ = nullptr;
|
SDL_GLContext glContext_ = nullptr;
|
||||||
bool shouldClose_ = false;
|
bool shouldClose_ = false;
|
||||||
|
|
@ -163,6 +170,9 @@ private:
|
||||||
|
|
||||||
// 模块配置事件监听器
|
// 模块配置事件监听器
|
||||||
std::unique_ptr<events::OnModuleConfig<AppConfig>::Listener> configListener_;
|
std::unique_ptr<events::OnModuleConfig<AppConfig>::Listener> configListener_;
|
||||||
|
|
||||||
|
// 渲染呈现事件监听器
|
||||||
|
std::unique_ptr<events::OnRenderPresent::Listener> renderPresentListener_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ namespace extra2d {
|
||||||
|
|
||||||
// 前向声明
|
// 前向声明
|
||||||
class Material;
|
class Material;
|
||||||
|
class Texture;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 材质参数信息
|
* @brief 材质参数信息
|
||||||
|
|
@ -25,6 +26,15 @@ struct MaterialParamInfo {
|
||||||
uint32_t size;
|
uint32_t size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 纹理槽位信息
|
||||||
|
*/
|
||||||
|
struct TextureSlot {
|
||||||
|
TextureHandle handle;
|
||||||
|
uint32_t slot;
|
||||||
|
std::string uniformName;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 材质布局类
|
* @brief 材质布局类
|
||||||
*
|
*
|
||||||
|
|
@ -77,8 +87,36 @@ private:
|
||||||
/**
|
/**
|
||||||
* @brief 材质类
|
* @brief 材质类
|
||||||
*
|
*
|
||||||
* 管理着色器和材质参数
|
* 作为着色器和纹理的中间层,管理:
|
||||||
* 支持通过 UBO 高效上传材质数据到 GPU
|
* - 着色器程序
|
||||||
|
* - 材质参数(通过 UBO 上传)
|
||||||
|
* - 纹理绑定
|
||||||
|
*
|
||||||
|
* 使用示例:
|
||||||
|
* @code
|
||||||
|
* // 创建自定义着色器
|
||||||
|
* auto shader = makePtr<Shader>();
|
||||||
|
* shader->loadFromFile("custom.vert", "custom.frag");
|
||||||
|
*
|
||||||
|
* // 创建材质布局
|
||||||
|
* auto layout = makePtr<MaterialLayout>();
|
||||||
|
* layout->addParam("uTintColor", MaterialParamType::Color);
|
||||||
|
* layout->addParam("uOpacity", MaterialParamType::Float);
|
||||||
|
* layout->finalize();
|
||||||
|
*
|
||||||
|
* // 创建材质
|
||||||
|
* auto material = makePtr<Material>();
|
||||||
|
* 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 {
|
class Material : public RefCounted {
|
||||||
public:
|
public:
|
||||||
|
|
@ -87,6 +125,10 @@ public:
|
||||||
*/
|
*/
|
||||||
Material();
|
Material();
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// 着色器和布局设置
|
||||||
|
// ========================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 设置材质布局
|
* @brief 设置材质布局
|
||||||
* @param layout 材质布局
|
* @param layout 材质布局
|
||||||
|
|
@ -99,6 +141,16 @@ public:
|
||||||
*/
|
*/
|
||||||
void setShader(Ptr<Shader> shader);
|
void setShader(Ptr<Shader> shader);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取着色器
|
||||||
|
* @return 着色器
|
||||||
|
*/
|
||||||
|
Ptr<Shader> getShader() const { return shader_; }
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// 参数设置
|
||||||
|
// ========================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 设置 float 参数
|
* @brief 设置 float 参数
|
||||||
* @param name 参数名称
|
* @param name 参数名称
|
||||||
|
|
@ -134,27 +186,32 @@ public:
|
||||||
*/
|
*/
|
||||||
void setMat4(const std::string& name, const float* value);
|
void setMat4(const std::string& name, const float* value);
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// 纹理管理
|
||||||
|
// ========================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 设置纹理
|
* @brief 设置纹理
|
||||||
* @param name 参数名称
|
* @param uniformName 着色器中的采样器 uniform 名称
|
||||||
* @param texture 纹理句柄
|
* @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 应用材质
|
* @brief 获取所有纹理槽位
|
||||||
*
|
* @return 纹理槽位列表
|
||||||
* 绑定着色器、上传 UBO 数据、绑定纹理
|
|
||||||
* @param uboManager UBO 管理器
|
|
||||||
*/
|
*/
|
||||||
void apply(UniformBufferManager& uboManager);
|
const std::vector<TextureSlot>& getTextures() const { return textures_; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取着色器
|
* @brief 清除所有纹理
|
||||||
* @return 着色器
|
|
||||||
*/
|
*/
|
||||||
Ptr<Shader> getShader() const { return shader_; }
|
void clearTextures();
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// 数据访问
|
||||||
|
// ========================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取材质数据指针
|
* @brief 获取材质数据指针
|
||||||
|
|
@ -168,11 +225,23 @@ public:
|
||||||
*/
|
*/
|
||||||
uint32_t getDataSize() const { return static_cast<uint32_t>(data_.size()); }
|
uint32_t getDataSize() const { return static_cast<uint32_t>(data_.size()); }
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// 应用材质(渲染时调用)
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 应用材质
|
||||||
|
*
|
||||||
|
* 绑定着色器、上传 UBO 数据
|
||||||
|
* @param uboManager UBO 管理器
|
||||||
|
*/
|
||||||
|
void apply(UniformBufferManager& uboManager);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ptr<MaterialLayout> layout_; // 材质布局
|
Ptr<MaterialLayout> layout_; // 材质布局
|
||||||
Ptr<Shader> shader_; // 着色器
|
Ptr<Shader> shader_; // 着色器
|
||||||
std::vector<uint8_t> data_; // 材质数据
|
std::vector<uint8_t> data_; // 材质数据(UBO 缓冲)
|
||||||
std::vector<std::pair<TextureHandle, uint32_t>> textures_; // 纹理列表
|
std::vector<TextureSlot> textures_; // 纹理槽位列表
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -93,42 +93,50 @@ inline uint32_t getMaterialParamSize(MaterialParamType type) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 渲染命令结构(32字节对齐)
|
* @brief 渲染命令结构
|
||||||
*
|
*
|
||||||
* 使用值类型设计,避免智能指针开销
|
* 使用值类型设计,避免智能指针开销
|
||||||
* 通过资源句柄引用实际资源
|
* 通过资源句柄引用实际资源
|
||||||
*/
|
*/
|
||||||
struct alignas(32) RenderCommand {
|
struct RenderCommand {
|
||||||
RenderCommandType type; // 命令类型
|
RenderCommandType type; // 命令类型
|
||||||
uint32_t sortKey; // 排序键(材质ID + 层)
|
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 {
|
union {
|
||||||
// 绘制网格命令
|
DrawMeshData drawMesh;
|
||||||
struct {
|
DrawInstancedData drawInstanced;
|
||||||
MeshHandle mesh; // 网格句柄
|
ViewportData viewport;
|
||||||
MaterialHandle material; // 材质句柄
|
ClearData clear;
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -137,7 +145,40 @@ struct alignas(32) RenderCommand {
|
||||||
RenderCommand() : type(RenderCommandType::DrawMesh), sortKey(0) {
|
RenderCommand() : type(RenderCommandType::DrawMesh), sortKey(0) {
|
||||||
drawMesh.mesh = INVALID_MESH_HANDLE;
|
drawMesh.mesh = INVALID_MESH_HANDLE;
|
||||||
drawMesh.material = INVALID_MATERIAL_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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#include <renderer/shader.h>
|
#include <renderer/shader.h>
|
||||||
#include <renderer/texture.h>
|
#include <renderer/texture.h>
|
||||||
#include <renderer/uniform_buffer.h>
|
#include <renderer/uniform_buffer.h>
|
||||||
|
#include <renderer/viewport_adapter.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
@ -169,10 +170,23 @@ public:
|
||||||
/**
|
/**
|
||||||
* @brief 清除缓冲区
|
* @brief 清除缓冲区
|
||||||
* @param color 清除颜色
|
* @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);
|
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:
|
private:
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
// 事件处理器
|
// 事件处理器
|
||||||
|
|
@ -191,6 +205,12 @@ private:
|
||||||
*/
|
*/
|
||||||
void onRenderSubmit(const RenderCommand &cmd);
|
void onRenderSubmit(const RenderCommand &cmd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染设置相机事件处理
|
||||||
|
* @param viewProj 视图投影矩阵
|
||||||
|
*/
|
||||||
|
void onRenderSetCamera(const Mat4 &viewProj);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 渲染结束事件处理
|
* @brief 渲染结束事件处理
|
||||||
*
|
*
|
||||||
|
|
@ -341,16 +361,17 @@ private:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
HandlePool<Material> materialPool_; // 材质资源池
|
HandlePool<Material> materialPool_; // 材质资源池
|
||||||
HandlePool<Mesh> meshPool_; // 网格资源池
|
HandlePool<Mesh> meshPool_; // 网格资源池
|
||||||
HandlePool<Texture> texturePool_; // 纹理资源池
|
HandlePool<Texture> texturePool_; // 纹理资源池
|
||||||
|
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
// 命令缓冲区
|
// 命令缓冲区
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|
||||||
std::array<RenderCommand, MAX_RENDER_COMMANDS> commandBuffer_; // 预分配命令缓冲区
|
std::array<RenderCommand, MAX_RENDER_COMMANDS>
|
||||||
uint32 commandCount_ = 0; // 当前命令数量
|
commandBuffer_; // 预分配命令缓冲区
|
||||||
|
uint32 commandCount_ = 0; // 当前命令数量
|
||||||
|
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
// UBO 管理器
|
// UBO 管理器
|
||||||
|
|
@ -372,6 +393,7 @@ private:
|
||||||
|
|
||||||
events::OnRenderBegin::Listener onRenderBeginListener_;
|
events::OnRenderBegin::Listener onRenderBeginListener_;
|
||||||
events::OnRenderSubmit::Listener onRenderSubmitListener_;
|
events::OnRenderSubmit::Listener onRenderSubmitListener_;
|
||||||
|
events::OnRenderSetCamera::Listener onRenderSetCameraListener_;
|
||||||
events::OnRenderEnd::Listener onRenderEndListener_;
|
events::OnRenderEnd::Listener onRenderEndListener_;
|
||||||
events::OnResize::Listener onResizeListener_;
|
events::OnResize::Listener onResizeListener_;
|
||||||
events::OnShow::Listener onShowListener_;
|
events::OnShow::Listener onShowListener_;
|
||||||
|
|
@ -380,17 +402,17 @@ private:
|
||||||
// 状态标志
|
// 状态标志
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|
||||||
bool glInitialized_ = false; // GL 是否已初始化
|
bool glInitialized_ = false; // GL 是否已初始化
|
||||||
|
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
// 渲染统计
|
// 渲染统计
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|
||||||
struct Stats {
|
struct Stats {
|
||||||
uint32 commandsSubmitted = 0; // 提交的命令数
|
uint32 commandsSubmitted = 0; // 提交的命令数
|
||||||
uint32 commandsExecuted = 0; // 执行的命令数
|
uint32 commandsExecuted = 0; // 执行的命令数
|
||||||
uint32 drawCalls = 0; // 绘制调用次数
|
uint32 drawCalls = 0; // 绘制调用次数
|
||||||
uint32 batches = 0; // 批次数
|
uint32 batches = 0; // 批次数
|
||||||
} stats_;
|
} stats_;
|
||||||
|
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|
@ -399,6 +421,18 @@ private:
|
||||||
|
|
||||||
int32 viewportX_ = 0, viewportY_ = 0;
|
int32 viewportX_ = 0, viewportY_ = 0;
|
||||||
int32 viewportWidth_ = 0, viewportHeight_ = 0;
|
int32 viewportWidth_ = 0, viewportHeight_ = 0;
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
// 视口适配器
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
ViewportAdapter viewportAdapter_; // 视口适配器
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
// 相机矩阵
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
Mat4 viewProjectionMatrix_; // 当前视图投影矩阵
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,139 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <types/base/types.h>
|
||||||
|
#include <types/math/vec2.h>
|
||||||
|
#include <types/math/rect.h>
|
||||||
|
#include <types/math/color.h>
|
||||||
|
#include <glm/mat4x4.hpp>
|
||||||
|
|
||||||
|
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<float>(screenWidth_), static_cast<float>(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
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <types/ptr/ref_counted.h>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -0,0 +1,280 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <scene/component.h>
|
||||||
|
#include <types/math/rect.h>
|
||||||
|
#include <types/math/mat4.h>
|
||||||
|
#include <types/math/vec2.h>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <scene/component.h>
|
||||||
|
#include <renderer/render_types.h>
|
||||||
|
#include <types/math/color.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 精灵渲染组件
|
||||||
|
*
|
||||||
|
* 负责渲染2D精灵,通过事件提交渲染命令
|
||||||
|
*
|
||||||
|
* 使用材质作为纹理和着色器的中间层:
|
||||||
|
* - 材质包含着色器、参数和纹理
|
||||||
|
* - 如果只想使用默认着色器,可以设置纹理,组件会自动处理
|
||||||
|
*
|
||||||
|
* 使用示例:
|
||||||
|
* @code
|
||||||
|
* // 方式1:使用完整材质(推荐)
|
||||||
|
* auto material = makePtr<Material>();
|
||||||
|
* 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
|
||||||
|
|
@ -0,0 +1,167 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <scene/component.h>
|
||||||
|
#include <types/math/transform.h>
|
||||||
|
#include <types/math/vec2.h>
|
||||||
|
#include <types/math/mat4.h>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <scene/scene.h>
|
||||||
|
#include <types/ptr/intrusive_ptr.h>
|
||||||
|
#include <types/ptr/ref_counted.h>
|
||||||
|
#include <stack>
|
||||||
|
|
||||||
|
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> scene);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 替换当前场景
|
||||||
|
* @param scene 新场景
|
||||||
|
*/
|
||||||
|
void replaceScene(Ptr<Scene> scene);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 压入场景(场景栈)
|
||||||
|
* @param scene 场景
|
||||||
|
*/
|
||||||
|
void pushScene(Ptr<Scene> 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<Scene> runningScene_;
|
||||||
|
std::stack<Ptr<Scene>> sceneStack_;
|
||||||
|
CameraComponent* mainCamera_ = nullptr;
|
||||||
|
bool running_ = false;
|
||||||
|
bool needEnd_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,322 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <scene/component.h>
|
||||||
|
#include <types/math/transform.h>
|
||||||
|
#include <types/math/vec2.h>
|
||||||
|
#include <types/math/mat4.h>
|
||||||
|
#include <types/ptr/intrusive_ptr.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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<Node> child);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 移除子节点
|
||||||
|
* @param child 子节点
|
||||||
|
*/
|
||||||
|
void removeChild(Node* child);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从父节点移除
|
||||||
|
*/
|
||||||
|
void removeFromParent();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取父节点
|
||||||
|
* @return 父节点指针
|
||||||
|
*/
|
||||||
|
Node* getParent() const { return parent_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取所有子节点
|
||||||
|
* @return 子节点列表
|
||||||
|
*/
|
||||||
|
const std::vector<Ptr<Node>>& getChildren() const { return children_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 移除所有子节点
|
||||||
|
*/
|
||||||
|
void removeAllChildren();
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// 组件管理
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 添加组件
|
||||||
|
* @tparam T 组件类型
|
||||||
|
* @param component 组件
|
||||||
|
* @return 组件指针
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
T* addComponent(Ptr<T> component) {
|
||||||
|
static_assert(std::is_base_of<Component, T>::value, "T must be derived from Component");
|
||||||
|
T* ptr = component.get();
|
||||||
|
components_.push_back(component);
|
||||||
|
ptr->onAttach(this);
|
||||||
|
|
||||||
|
// 如果是 TransformComponent,缓存它
|
||||||
|
if (auto* transform = dynamic_cast<TransformComponent*>(ptr)) {
|
||||||
|
transform_ = transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取组件
|
||||||
|
* @tparam T 组件类型
|
||||||
|
* @return 组件指针,未找到返回 nullptr
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
T* getComponent() const {
|
||||||
|
for (const auto& comp : components_) {
|
||||||
|
if (auto* ptr = dynamic_cast<T*>(comp.get())) {
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 移除组件
|
||||||
|
* @tparam T 组件类型
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
void removeComponent() {
|
||||||
|
for (auto it = components_.begin(); it != components_.end(); ++it) {
|
||||||
|
if (dynamic_cast<T*>(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<Ptr<Node>> children_;
|
||||||
|
std::vector<Ptr<Component>> components_;
|
||||||
|
TransformComponent* transform_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <scene/node.h>
|
||||||
|
#include <types/ptr/intrusive_ptr.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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> node);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 移除子节点
|
||||||
|
* @param node 节点
|
||||||
|
*/
|
||||||
|
void removeChild(Node* node);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 移除所有子节点
|
||||||
|
*/
|
||||||
|
void removeAllChildren();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取所有子节点
|
||||||
|
* @return 子节点列表
|
||||||
|
*/
|
||||||
|
const std::vector<Ptr<Node>>& 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<Ptr<Node>> rootNodes_;
|
||||||
|
CameraComponent* mainCamera_ = nullptr;
|
||||||
|
bool entered_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <event/events.h>
|
||||||
|
#include <module/module.h>
|
||||||
|
#include <module/module_registry.h>
|
||||||
|
#include <scene/director.h>
|
||||||
|
|
||||||
|
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> director_;
|
||||||
|
|
||||||
|
// 事件监听器
|
||||||
|
events::OnUpdate::Listener onUpdateListener_;
|
||||||
|
events::OnRenderBegin::Listener onRenderBeginListener_;
|
||||||
|
events::OnRenderEnd::Listener onRenderEndListener_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <glm/mat4x4.hpp>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 4x4 矩阵类型
|
||||||
|
*
|
||||||
|
* 使用 glm::mat4 作为底层实现
|
||||||
|
*/
|
||||||
|
using Mat4 = glm::mat4;
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
#include <types/base/types.h>
|
#include <types/base/types.h>
|
||||||
#include <types/math/vec2.h>
|
#include <types/math/vec2.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
#include <glm/gtc/type_ptr.hpp>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
@ -27,18 +30,28 @@ struct Transform {
|
||||||
bool operator!=(const Transform& o) const { return !(*this == o); }
|
bool operator!=(const Transform& o) const { return !(*this == o); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 转换为 4x4 矩阵(列主序)
|
* @brief 转换为 4x4 矩阵(列主序),使用 glm 构建
|
||||||
* @param out 输出矩阵(16个float)
|
* @param out 输出矩阵(16个float)
|
||||||
|
*
|
||||||
|
* 变换顺序:缩放 -> 旋转 -> 平移
|
||||||
|
* 对于列向量,变换从右向左应用:v' = T * R * S * v
|
||||||
*/
|
*/
|
||||||
void toMatrix(float* out) const {
|
void toMatrix(float* out) const {
|
||||||
float c = std::cos(rot);
|
// 使用 glm 构建矩阵
|
||||||
float s = std::sin(rot);
|
// 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));
|
||||||
|
|
||||||
// 列主序矩阵
|
// 复制到输出数组(glm 已经是列主序)
|
||||||
out[0] = scale.x * c; out[1] = scale.x * s; out[2] = 0.0f; out[3] = 0.0f;
|
const float* src = glm::value_ptr(matrix);
|
||||||
out[4] = -scale.y * s; out[5] = scale.y * c; out[6] = 0.0f; out[7] = 0.0f;
|
for (int i = 0; i < 16; ++i) {
|
||||||
out[8] = 0.0f; out[9] = 0.0f; out[10] = 1.0f; out[11] = 0.0f;
|
out[i] = src[i];
|
||||||
out[12] = pos.x; out[13] = pos.y; out[14] = 0.0f; out[15] = 1.0f;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static const Transform Identity;
|
static const Transform Identity;
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,36 @@
|
||||||
#version 320 es
|
#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 vec2 vTexCoord;
|
||||||
in vec4 vColor;
|
in vec4 vColor;
|
||||||
|
|
||||||
// 纹理采样器
|
// 纹理采样器
|
||||||
uniform sampler2D uTexture;
|
uniform sampler2D uTexture;
|
||||||
|
|
||||||
|
// 材质参数
|
||||||
|
uniform vec4 uTintColor;
|
||||||
|
uniform float uOpacity;
|
||||||
|
|
||||||
// 输出颜色
|
// 输出颜色
|
||||||
out vec4 fragColor;
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 片段着色器入口
|
||||||
|
*
|
||||||
|
* 采样纹理并与顶点颜色、色调和透明度混合
|
||||||
|
*/
|
||||||
void main() {
|
void main() {
|
||||||
// 采样纹理
|
// 采样纹理
|
||||||
vec4 texColor = texture(uTexture, vTexCoord);
|
vec4 texColor = texture(uTexture, vTexCoord);
|
||||||
|
|
||||||
// 应用顶点颜色、材质颜色和透明度
|
// 混合:纹理 * 顶点颜色 * 色调
|
||||||
fragColor = texColor * vColor * uMaterial.tintColor;
|
fragColor = texColor * vColor * uTintColor;
|
||||||
fragColor.a *= uMaterial.opacity;
|
|
||||||
|
|
||||||
// 丢弃完全透明的像素
|
// 应用透明度
|
||||||
|
fragColor.a *= uOpacity;
|
||||||
|
|
||||||
|
// Alpha 测试:丢弃几乎透明的像素
|
||||||
if (fragColor.a < 0.01) {
|
if (fragColor.a < 0.01) {
|
||||||
discard;
|
discard;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,14 @@
|
||||||
#version 320 es
|
#version 320 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
// 全局 UBO
|
// 视图投影矩阵
|
||||||
layout(std140, binding = 0) uniform GlobalUBO {
|
uniform mat4 uViewProjection;
|
||||||
mat4 viewProjection;
|
|
||||||
vec2 screenSize;
|
|
||||||
float time;
|
|
||||||
} uGlobal;
|
|
||||||
|
|
||||||
// 实例 UBO
|
// 模型矩阵
|
||||||
layout(std140, binding = 2) uniform InstanceUBO {
|
uniform mat4 uModelMatrix;
|
||||||
mat4 transforms[256];
|
|
||||||
} uInstances;
|
// 顶点颜色(覆盖顶点属性中的颜色)
|
||||||
|
uniform vec4 uColor;
|
||||||
|
|
||||||
// 顶点属性
|
// 顶点属性
|
||||||
layout(location = 0) in vec2 aPosition;
|
layout(location = 0) in vec2 aPosition;
|
||||||
|
|
@ -21,14 +19,15 @@ layout(location = 2) in vec4 aColor;
|
||||||
out vec2 vTexCoord;
|
out vec2 vTexCoord;
|
||||||
out vec4 vColor;
|
out vec4 vColor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 顶点着色器入口
|
||||||
|
*
|
||||||
|
* 计算顶点在裁剪空间中的位置,
|
||||||
|
* 并传递纹理坐标和顶点颜色到片段着色器
|
||||||
|
*/
|
||||||
void main() {
|
void main() {
|
||||||
// 获取实例变换矩阵
|
gl_Position = uViewProjection * uModelMatrix * vec4(aPosition, 0.0, 1.0);
|
||||||
mat4 instanceTransform = uInstances.transforms[gl_InstanceID];
|
|
||||||
|
|
||||||
// 计算最终位置
|
|
||||||
gl_Position = uGlobal.viewProjection * instanceTransform * vec4(aPosition, 0.0, 1.0);
|
|
||||||
|
|
||||||
// 传递纹理坐标和颜色
|
|
||||||
vTexCoord = aTexCoord;
|
vTexCoord = aTexCoord;
|
||||||
vColor = aColor;
|
// 使用 uniform 颜色覆盖顶点属性颜色
|
||||||
|
vColor = uColor;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,9 @@ void Context::tick(float dt) {
|
||||||
|
|
||||||
// 3. 渲染结束并执行绘制
|
// 3. 渲染结束并执行绘制
|
||||||
events::OnRenderEnd::emit();
|
events::OnRenderEnd::emit();
|
||||||
|
|
||||||
|
// 4. 呈现渲染结果(交换缓冲区)
|
||||||
|
events::OnRenderPresent::emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
ModuleRegistry &Context::modules() { return ModuleRegistry::instance(); }
|
ModuleRegistry &Context::modules() { return ModuleRegistry::instance(); }
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,11 @@ bool WindowModule::init() {
|
||||||
configListener_->bind(
|
configListener_->bind(
|
||||||
[this](const AppConfig &config) { this->onModuleConfig(config); });
|
[this](const AppConfig &config) { this->onModuleConfig(config); });
|
||||||
|
|
||||||
|
// 监听渲染呈现事件
|
||||||
|
renderPresentListener_ =
|
||||||
|
std::make_unique<events::OnRenderPresent::Listener>();
|
||||||
|
renderPresentListener_->bind([this]() { this->onRenderPresent(); });
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,6 +95,7 @@ bool WindowModule::pollEvents() {
|
||||||
while (SDL_PollEvent(&evt)) {
|
while (SDL_PollEvent(&evt)) {
|
||||||
switch (evt.type) {
|
switch (evt.type) {
|
||||||
case SDL_QUIT:
|
case SDL_QUIT:
|
||||||
|
E2D_LOG_INFO("SDL_QUIT event received");
|
||||||
shouldClose_ = true;
|
shouldClose_ = true;
|
||||||
// 发送窗口关闭事件
|
// 发送窗口关闭事件
|
||||||
events::OnClose::emit();
|
events::OnClose::emit();
|
||||||
|
|
@ -280,6 +286,10 @@ void WindowModule::swapBuffers() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WindowModule::onRenderPresent() {
|
||||||
|
swapBuffers();
|
||||||
|
}
|
||||||
|
|
||||||
void *WindowModule::getGLContext() const { return glContext_; }
|
void *WindowModule::getGLContext() const { return glContext_; }
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,17 @@
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ========================================
|
||||||
// MaterialLayout 实现
|
// MaterialLayout 实现
|
||||||
|
// ========================================
|
||||||
|
|
||||||
MaterialLayout::MaterialLayout() = default;
|
MaterialLayout::MaterialLayout() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 添加参数到布局
|
||||||
|
* @param name 参数名称
|
||||||
|
* @param type 参数类型
|
||||||
|
*/
|
||||||
void MaterialLayout::addParam(const std::string& name, MaterialParamType type) {
|
void MaterialLayout::addParam(const std::string& name, MaterialParamType type) {
|
||||||
if (finalized_) {
|
if (finalized_) {
|
||||||
E2D_LOG_WARN("Cannot add param to finalized MaterialLayout");
|
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;
|
params_[name] = info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 完成布局定义,计算 std140 布局偏移
|
||||||
|
*
|
||||||
|
* std140 布局规则:
|
||||||
|
* - 标量和向量:偏移必须是其大小的倍数
|
||||||
|
* - 数组和结构体:偏移必须是 16 的倍数
|
||||||
|
* - vec3 后面需要填充到 16 字节边界
|
||||||
|
*/
|
||||||
void MaterialLayout::finalize() {
|
void MaterialLayout::finalize() {
|
||||||
if (finalized_) return;
|
if (finalized_) return;
|
||||||
|
|
||||||
// 计算 std140 布局的偏移
|
|
||||||
// std140 规则:
|
|
||||||
// - 标量和向量:偏移必须是其大小的倍数
|
|
||||||
// - 数组和结构体:偏移必须是 16 的倍数
|
|
||||||
// - vec3 后面需要填充到 16 字节边界
|
|
||||||
|
|
||||||
uint32_t offset = 0;
|
uint32_t offset = 0;
|
||||||
for (auto& pair : params_) {
|
for (auto& pair : params_) {
|
||||||
auto& info = pair.second;
|
auto& info = pair.second;
|
||||||
|
|
||||||
// 对齐到参数大小的倍数
|
// 计算对齐要求
|
||||||
uint32_t alignment = info.size;
|
uint32_t alignment = 4; // 默认 4 字节对齐
|
||||||
if (info.type == MaterialParamType::Mat4) {
|
|
||||||
alignment = 16; // mat4 需要 16 字节对齐
|
switch (info.type) {
|
||||||
} else if (info.type == MaterialParamType::Vec3) {
|
case MaterialParamType::Float:
|
||||||
alignment = 16; // vec3 在 std140 中占 16 字节
|
alignment = 4; // float: 4 字节对齐
|
||||||
} else if (alignment < 4) {
|
break;
|
||||||
alignment = 4; // 最小 4 字节对齐
|
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;
|
finalized_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取参数信息
|
||||||
|
* @param name 参数名称
|
||||||
|
* @return 参数信息指针,不存在返回 nullptr
|
||||||
|
*/
|
||||||
const MaterialParamInfo* MaterialLayout::getParam(const std::string& name) const {
|
const MaterialParamInfo* MaterialLayout::getParam(const std::string& name) const {
|
||||||
auto it = params_.find(name);
|
auto it = params_.find(name);
|
||||||
if (it != params_.end()) {
|
if (it != params_.end()) {
|
||||||
|
|
@ -65,10 +92,16 @@ const MaterialParamInfo* MaterialLayout::getParam(const std::string& name) const
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
// Material 实现
|
// Material 实现
|
||||||
|
// ========================================
|
||||||
|
|
||||||
Material::Material() = default;
|
Material::Material() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置材质布局
|
||||||
|
* @param layout 材质布局
|
||||||
|
*/
|
||||||
void Material::setLayout(Ptr<MaterialLayout> layout) {
|
void Material::setLayout(Ptr<MaterialLayout> layout) {
|
||||||
layout_ = layout;
|
layout_ = layout;
|
||||||
if (layout_ && layout_->isFinalized()) {
|
if (layout_ && layout_->isFinalized()) {
|
||||||
|
|
@ -76,10 +109,19 @@ void Material::setLayout(Ptr<MaterialLayout> layout) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置着色器
|
||||||
|
* @param shader 着色器
|
||||||
|
*/
|
||||||
void Material::setShader(Ptr<Shader> shader) {
|
void Material::setShader(Ptr<Shader> shader) {
|
||||||
shader_ = shader;
|
shader_ = shader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置 float 参数
|
||||||
|
* @param name 参数名称
|
||||||
|
* @param value 值
|
||||||
|
*/
|
||||||
void Material::setFloat(const std::string& name, float value) {
|
void Material::setFloat(const std::string& name, float value) {
|
||||||
if (!layout_) return;
|
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) {
|
void Material::setVec2(const std::string& name, const Vec2& value) {
|
||||||
if (!layout_) return;
|
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) {
|
void Material::setVec4(const std::string& name, float x, float y, float z, float w) {
|
||||||
if (!layout_) return;
|
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) {
|
void Material::setColor(const std::string& name, const Color& value) {
|
||||||
if (!layout_) return;
|
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) {
|
void Material::setMat4(const std::string& name, const float* value) {
|
||||||
if (!layout_) return;
|
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) {
|
/**
|
||||||
// 纹理存储在单独的列表中
|
* @brief 设置纹理
|
||||||
textures_.push_back({texture, slot});
|
* @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) {
|
void Material::apply(UniformBufferManager& uboManager) {
|
||||||
if (!shader_) return;
|
if (!shader_) return;
|
||||||
|
|
||||||
// 绑定着色器
|
// 绑定着色器
|
||||||
shader_->bind();
|
shader_->bind();
|
||||||
|
|
||||||
|
// 设置 Uniform Block 绑定
|
||||||
|
shader_->setUniformBlock("GlobalUBO", GLOBAL_UBO_BINDING);
|
||||||
|
shader_->setUniformBlock("MaterialUBO", MATERIAL_UBO_BINDING);
|
||||||
|
shader_->setUniformBlock("InstanceUBO", INSTANCE_UBO_BINDING);
|
||||||
|
|
||||||
// 上传材质数据到 UBO
|
// 上传材质数据到 UBO
|
||||||
if (!data_.empty()) {
|
if (!data_.empty()) {
|
||||||
auto* ubo = uboManager.acquireMaterialUBO(static_cast<uint32_t>(data_.size()));
|
auto* ubo = uboManager.acquireMaterialUBO(static_cast<uint32_t>(data_.size()));
|
||||||
|
|
@ -145,14 +243,6 @@ void Material::apply(UniformBufferManager& uboManager) {
|
||||||
ubo->bind(MATERIAL_UBO_BINDING);
|
ubo->bind(MATERIAL_UBO_BINDING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置 Uniform Block 绑定(如果着色器支持)
|
|
||||||
shader_->setUniformBlock("MaterialUBO", MATERIAL_UBO_BINDING);
|
|
||||||
|
|
||||||
// TODO: 绑定纹理
|
|
||||||
// for (const auto& [texture, slot] : textures_) {
|
|
||||||
// // 通过纹理句柄获取纹理并绑定
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ void Mesh::setVertices(const Vertex* vertices, uint32_t count) {
|
||||||
if (vao_ == 0) {
|
if (vao_ == 0) {
|
||||||
glGenVertexArrays(1, &vao_);
|
glGenVertexArrays(1, &vao_);
|
||||||
glGenBuffers(1, &vbo_);
|
glGenBuffers(1, &vbo_);
|
||||||
|
|
||||||
|
E2D_LOG_INFO("Created VAO: {}, VBO: {}", vao_, vbo_);
|
||||||
}
|
}
|
||||||
|
|
||||||
glBindVertexArray(vao_);
|
glBindVertexArray(vao_);
|
||||||
|
|
@ -56,6 +58,8 @@ void Mesh::setVertices(const Vertex* vertices, uint32_t count) {
|
||||||
|
|
||||||
glBindVertexArray(0);
|
glBindVertexArray(0);
|
||||||
vertexCount_ = count;
|
vertexCount_ = count;
|
||||||
|
|
||||||
|
E2D_LOG_INFO("Set {} vertices for VAO {}", count, vao_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mesh::setIndices(const uint16_t* indices, uint32_t count) {
|
void Mesh::setIndices(const uint16_t* indices, uint32_t count) {
|
||||||
|
|
@ -102,11 +106,31 @@ void Mesh::unbind() const {
|
||||||
void Mesh::draw() const {
|
void Mesh::draw() const {
|
||||||
if (vao_ == 0) return;
|
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) {
|
if (indexCount_ > 0) {
|
||||||
glDrawElements(GL_TRIANGLES, indexCount_, GL_UNSIGNED_SHORT, nullptr);
|
glDrawElements(GL_TRIANGLES, indexCount_, GL_UNSIGNED_SHORT, nullptr);
|
||||||
} else {
|
} else {
|
||||||
glDrawArrays(GL_TRIANGLES, 0, vertexCount_);
|
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 {
|
void Mesh::drawInstanced(uint32_t instanceCount) const {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,12 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <event/events.h>
|
#include <event/events.h>
|
||||||
|
#include <glm/gtc/type_ptr.hpp>
|
||||||
#include <renderer/renderer_module.h>
|
#include <renderer/renderer_module.h>
|
||||||
#include <utils/logger.h>
|
#include <utils/logger.h>
|
||||||
|
|
||||||
|
// SDL for window size query
|
||||||
|
#include <SDL.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
RendererModule::RendererModule() = default;
|
RendererModule::RendererModule() = default;
|
||||||
|
|
@ -93,6 +97,8 @@ bool RendererModule::init() {
|
||||||
onRenderBeginListener_.bind([this]() { onRenderBegin(); });
|
onRenderBeginListener_.bind([this]() { onRenderBegin(); });
|
||||||
onRenderSubmitListener_.bind(
|
onRenderSubmitListener_.bind(
|
||||||
[this](const RenderCommand &cmd) { onRenderSubmit(cmd); });
|
[this](const RenderCommand &cmd) { onRenderSubmit(cmd); });
|
||||||
|
onRenderSetCameraListener_.bind(
|
||||||
|
[this](const Mat4 &viewProj) { onRenderSetCamera(viewProj); });
|
||||||
onRenderEndListener_.bind([this]() { onRenderEnd(); });
|
onRenderEndListener_.bind([this]() { onRenderEnd(); });
|
||||||
onResizeListener_.bind([this](int32_t w, int32_t h) { onResize(w, h); });
|
onResizeListener_.bind([this](int32_t w, int32_t h) { onResize(w, h); });
|
||||||
|
|
||||||
|
|
@ -122,11 +128,23 @@ void RendererModule::onWindowShow() {
|
||||||
return;
|
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<int32>(windowWidth),
|
||||||
|
static_cast<int32>(windowHeight));
|
||||||
|
|
||||||
// 启用深度测试和混合
|
// 禁用深度测试和背面剔除(2D渲染不需要)
|
||||||
glEnable(GL_DEPTH_TEST);
|
// glDisable(GL_DEPTH_TEST);
|
||||||
|
// glDisable(GL_CULL_FACE);
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
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;
|
viewportY_ = y;
|
||||||
viewportWidth_ = width;
|
viewportWidth_ = width;
|
||||||
viewportHeight_ = height;
|
viewportHeight_ = height;
|
||||||
glViewport(x, y, width, height);
|
|
||||||
|
// 更新视口适配器
|
||||||
|
viewportAdapter_.update(width, height);
|
||||||
|
|
||||||
|
// 获取适配后的视口
|
||||||
|
auto result = viewportAdapter_.getResult();
|
||||||
|
glViewport(static_cast<GLint>(result.viewport.x),
|
||||||
|
static_cast<GLint>(result.viewport.y),
|
||||||
|
static_cast<GLsizei>(result.viewport.w),
|
||||||
|
static_cast<GLsizei>(result.viewport.h));
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererModule::clear(const Color &color, uint32_t flags) {
|
void RendererModule::clear(const Color &color, uint32_t flags) {
|
||||||
|
|
@ -253,6 +280,10 @@ void RendererModule::onRenderBegin() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清除屏幕(黑色背景)
|
||||||
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
// 清空命令缓冲区
|
// 清空命令缓冲区
|
||||||
commandCount_ = 0;
|
commandCount_ = 0;
|
||||||
|
|
||||||
|
|
@ -280,6 +311,19 @@ void RendererModule::onRenderSubmit(const RenderCommand &cmd) {
|
||||||
stats_.commandsSubmitted++;
|
stats_.commandsSubmitted++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RendererModule::onRenderSetCamera(const Mat4 &viewProj) {
|
||||||
|
// 如果 GL 未初始化,跳过
|
||||||
|
if (!glInitialized_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存视图投影矩阵
|
||||||
|
viewProjectionMatrix_ = viewProj;
|
||||||
|
|
||||||
|
// 更新全局 UBO 中的视图投影矩阵
|
||||||
|
uniformManager_.updateGlobalUBO(&viewProj, sizeof(Mat4));
|
||||||
|
}
|
||||||
|
|
||||||
void RendererModule::onRenderEnd() {
|
void RendererModule::onRenderEnd() {
|
||||||
// 如果 GL 未初始化,跳过渲染
|
// 如果 GL 未初始化,跳过渲染
|
||||||
if (!glInitialized_) {
|
if (!glInitialized_) {
|
||||||
|
|
@ -361,50 +405,81 @@ void RendererModule::drawBatch(uint32_t start, uint32_t count,
|
||||||
MaterialHandle materialHandle,
|
MaterialHandle materialHandle,
|
||||||
MeshHandle meshHandle) {
|
MeshHandle meshHandle) {
|
||||||
auto material = getMaterial(materialHandle);
|
auto material = getMaterial(materialHandle);
|
||||||
|
// 如果材质无效,使用默认材质
|
||||||
|
if (!material) {
|
||||||
|
material = getMaterial(defaultMaterialHandle_);
|
||||||
|
}
|
||||||
|
|
||||||
auto mesh = getMesh(meshHandle);
|
auto mesh = getMesh(meshHandle);
|
||||||
|
// 如果网格无效,使用默认四边形
|
||||||
|
if (!mesh) {
|
||||||
|
mesh = getMesh(defaultQuadHandle_);
|
||||||
|
}
|
||||||
|
|
||||||
if (!material || !mesh)
|
if (!material || !mesh)
|
||||||
return;
|
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<int>(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();
|
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();
|
mesh->draw();
|
||||||
} else {
|
stats_.drawCalls++;
|
||||||
// 批量绘制 - 使用实例化渲染
|
|
||||||
// 上传变换矩阵到 UBO
|
|
||||||
std::vector<float> 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<uint32_t>(transforms.size() * sizeof(float)));
|
|
||||||
if (instanceUBO) {
|
|
||||||
instanceUBO->update(
|
|
||||||
transforms.data(),
|
|
||||||
static_cast<uint32_t>(transforms.size() * sizeof(float)));
|
|
||||||
instanceUBO->bind(INSTANCE_UBO_BINDING);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 实例化渲染
|
|
||||||
mesh->drawInstanced(count);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stats_.drawCalls++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererModule::executeCommand(const RenderCommand &cmd) {
|
void RendererModule::executeCommand(const RenderCommand &cmd) {
|
||||||
|
|
|
||||||
|
|
@ -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 processedVS = addVersionIfNeeded(vsSource, GL_VERTEX_SHADER);
|
||||||
std::string processedFS = addVersionIfNeeded(fsSource, GL_FRAGMENT_SHADER);
|
std::string processedFS = addVersionIfNeeded(fsSource, GL_FRAGMENT_SHADER);
|
||||||
|
|
||||||
|
E2D_LOG_INFO("Compiling vertex shader...");
|
||||||
// 编译顶点着色器
|
// 编译顶点着色器
|
||||||
GLuint vs = compileShader(GL_VERTEX_SHADER, processedVS);
|
GLuint vs = compileShader(GL_VERTEX_SHADER, processedVS);
|
||||||
if (vs == 0) {
|
if (vs == 0) {
|
||||||
|
E2D_LOG_ERROR("Vertex shader compilation failed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
E2D_LOG_INFO("Compiling fragment shader...");
|
||||||
// 编译片段着色器
|
// 编译片段着色器
|
||||||
GLuint fs = compileShader(GL_FRAGMENT_SHADER, processedFS);
|
GLuint fs = compileShader(GL_FRAGMENT_SHADER, processedFS);
|
||||||
if (fs == 0) {
|
if (fs == 0) {
|
||||||
|
E2D_LOG_ERROR("Fragment shader compilation failed");
|
||||||
glDeleteShader(vs);
|
glDeleteShader(vs);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
E2D_LOG_INFO("Linking shader program...");
|
||||||
// 链接程序
|
// 链接程序
|
||||||
if (!linkProgram(vs, fs)) {
|
if (!linkProgram(vs, fs)) {
|
||||||
|
E2D_LOG_ERROR("Shader program linking failed");
|
||||||
glDeleteShader(vs);
|
glDeleteShader(vs);
|
||||||
glDeleteShader(fs);
|
glDeleteShader(fs);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -76,7 +82,7 @@ bool Shader::loadFromSource(const std::string& vsSource, const std::string& fsSo
|
||||||
glDeleteShader(vs);
|
glDeleteShader(vs);
|
||||||
glDeleteShader(fs);
|
glDeleteShader(fs);
|
||||||
|
|
||||||
E2D_LOG_DEBUG("Shader program created successfully");
|
E2D_LOG_INFO("Shader program created successfully, program ID: {}", program_);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,6 +137,19 @@ void Shader::setMat4(const std::string& name, const float* value) {
|
||||||
GLint location = getUniformLocation(name);
|
GLint location = getUniformLocation(name);
|
||||||
if (location != -1) {
|
if (location != -1) {
|
||||||
glUniformMatrix4fv(location, 1, GL_FALSE, value);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ bool UniformBuffer::create(uint32_t size, uint32_t binding) {
|
||||||
|
|
||||||
glBindBuffer(GL_UNIFORM_BUFFER, ubo_);
|
glBindBuffer(GL_UNIFORM_BUFFER, ubo_);
|
||||||
glBufferData(GL_UNIFORM_BUFFER, size, nullptr, GL_DYNAMIC_DRAW);
|
glBufferData(GL_UNIFORM_BUFFER, size, nullptr, GL_DYNAMIC_DRAW);
|
||||||
|
glBindBufferBase(GL_UNIFORM_BUFFER, binding, ubo_);
|
||||||
glBindBuffer(GL_UNIFORM_BUFFER, 0);
|
glBindBuffer(GL_UNIFORM_BUFFER, 0);
|
||||||
|
|
||||||
E2D_LOG_DEBUG("UBO created: size={}, binding={}", size, binding);
|
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) {
|
void UniformBufferManager::updateGlobalUBO(const void* data, uint32_t size) {
|
||||||
if (globalUBO_) {
|
if (globalUBO_) {
|
||||||
globalUBO_->update(data, size);
|
globalUBO_->update(data, size);
|
||||||
|
globalUBO_->bind(GLOBAL_UBO_BINDING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,431 @@
|
||||||
|
#include <algorithm>
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
#include <renderer/viewport_adapter.h>
|
||||||
|
|
||||||
|
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<float>(screenWidth_) / static_cast<float>(screenHeight_);
|
||||||
|
|
||||||
|
if (screenAspect > logicAspect) {
|
||||||
|
// 屏幕更宽,以高度为基准
|
||||||
|
result_.uniformScale =
|
||||||
|
static_cast<float>(screenHeight_) / config_.logicHeight;
|
||||||
|
result_.scaleX = result_.uniformScale;
|
||||||
|
result_.scaleY = result_.uniformScale;
|
||||||
|
result_.viewport.w = config_.logicWidth * result_.uniformScale;
|
||||||
|
result_.viewport.h = static_cast<float>(screenHeight_);
|
||||||
|
result_.offset.x = (screenWidth_ - result_.viewport.w) / 2.0f;
|
||||||
|
result_.offset.y = 0.0f;
|
||||||
|
} else {
|
||||||
|
// 屏幕更高,以宽度为基准
|
||||||
|
result_.uniformScale =
|
||||||
|
static_cast<float>(screenWidth_) / config_.logicWidth;
|
||||||
|
result_.scaleX = result_.uniformScale;
|
||||||
|
result_.scaleY = result_.uniformScale;
|
||||||
|
result_.viewport.w = static_cast<float>(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<float>(screenWidth_) - result_.viewport.w,
|
||||||
|
static_cast<float>(screenHeight_) -
|
||||||
|
result_.viewport.h);
|
||||||
|
|
||||||
|
calculateLetterbox();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 计算拉伸适配模式
|
||||||
|
*
|
||||||
|
* 拉伸逻辑视口以填满整个屏幕
|
||||||
|
*/
|
||||||
|
void ViewportAdapter::calculateStretch() {
|
||||||
|
result_.scaleX = static_cast<float>(screenWidth_) / config_.logicWidth;
|
||||||
|
result_.scaleY = static_cast<float>(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<float>(screenWidth_);
|
||||||
|
result_.viewport.h = static_cast<float>(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<float>(screenWidth_) / config_.logicWidth;
|
||||||
|
float scaleY = static_cast<float>(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<float>(screenWidth_) - displayWidth,
|
||||||
|
static_cast<float>(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<float>(screenWidth_);
|
||||||
|
float screenH = static_cast<float>(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
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
#include <scene/component.h>
|
||||||
|
#include <scene/node.h>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -0,0 +1,313 @@
|
||||||
|
#include <algorithm>
|
||||||
|
#include <glm/gtc/matrix_inverse.hpp>
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
#include <glm/gtc/type_ptr.hpp>
|
||||||
|
#include <scene/components/camera_component.h>
|
||||||
|
#include <scene/node.h>
|
||||||
|
#include <utils/logger.h>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
#include <event/events.h>
|
||||||
|
#include <scene/components/sprite_renderer.h>
|
||||||
|
#include <scene/node.h>
|
||||||
|
#include <utils/logger.h>
|
||||||
|
|
||||||
|
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<uint32_t>(material_ & 0xFFFFFFFF);
|
||||||
|
uint32_t textureId = static_cast<uint32_t>(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
|
||||||
|
|
@ -0,0 +1,163 @@
|
||||||
|
#include <scene/components/transform_component.h>
|
||||||
|
#include <scene/node.h>
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
#include <glm/gtc/constants.hpp>
|
||||||
|
|
||||||
|
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<float>() / 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
|
||||||
|
|
@ -0,0 +1,154 @@
|
||||||
|
#include <scene/director.h>
|
||||||
|
#include <scene/components/camera_component.h>
|
||||||
|
#include <event/events.h>
|
||||||
|
#include <utils/logger.h>
|
||||||
|
|
||||||
|
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> 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> scene) {
|
||||||
|
if (!scene) return;
|
||||||
|
|
||||||
|
// 结束当前场景
|
||||||
|
if (runningScene_) {
|
||||||
|
runningScene_->onExit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 替换场景
|
||||||
|
runningScene_ = scene;
|
||||||
|
runningScene_->onEnter();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Director::pushScene(Ptr<Scene> 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
|
||||||
|
|
@ -0,0 +1,207 @@
|
||||||
|
#include <scene/node.h>
|
||||||
|
#include <scene/components/transform_component.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
Node::Node() {
|
||||||
|
// 自动添加 TransformComponent
|
||||||
|
auto transform = makePtr<TransformComponent>();
|
||||||
|
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<Node> 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
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
#include <scene/scene.h>
|
||||||
|
#include <scene/components/camera_component.h>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
Scene::Scene() {
|
||||||
|
}
|
||||||
|
|
||||||
|
Scene::~Scene() {
|
||||||
|
if (entered_) {
|
||||||
|
onExit();
|
||||||
|
}
|
||||||
|
removeAllChildren();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scene::addChild(Ptr<Node> 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<Node*> 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<Node*> 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
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
#include <scene/scene_module.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
SceneModule::SceneModule() = default;
|
||||||
|
|
||||||
|
SceneModule::~SceneModule() {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneModule::SceneModule(SceneModule &&) noexcept = default;
|
||||||
|
|
||||||
|
SceneModule &SceneModule::operator=(SceneModule &&) noexcept = default;
|
||||||
|
|
||||||
|
bool SceneModule::init() {
|
||||||
|
// 创建导演
|
||||||
|
director_ = makePtr<Director>();
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
#include <types/math/vec2.h>
|
||||||
|
#include <types/math/rect.h>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -94,6 +94,7 @@ define_extra2d_engine()
|
||||||
-- 示例程序目标(作为子项目)
|
-- 示例程序目标(作为子项目)
|
||||||
if is_config("examples","true") then
|
if is_config("examples","true") then
|
||||||
includes("examples/hello_world", {rootdir = "examples/hello_world"})
|
includes("examples/hello_world", {rootdir = "examples/hello_world"})
|
||||||
|
includes("examples/scene_graph_demo", {rootdir = "examples/scene_graph_demo"})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue