feat(渲染): 实现节点层级变换支持

refactor(场景图): 简化形状节点渲染逻辑

feat(输入): 添加输入事件到事件服务的连接

docs(模块系统): 更新文档说明模块配置和平台支持

test(示例): 添加场景图测试示例展示节点变换
This commit is contained in:
ChestnutYueyue 2026-02-15 13:32:42 +08:00
parent 8c56c29cd2
commit 6c6cac55f7
8 changed files with 728 additions and 505 deletions

View File

@ -22,7 +22,9 @@
#include <extra2d/platform/iinput.h>
#include <extra2d/platform/iwindow.h>
#include <extra2d/platform/keys.h>
#include <extra2d/platform/input_module.h>
#include <extra2d/platform/platform_module.h>
#include <extra2d/platform/window_module.h>
// Graphics
#include <extra2d/graphics/camera.h>
@ -54,6 +56,12 @@
#include <extra2d/utils/random.h>
#include <extra2d/utils/timer.h>
// Services
#include <extra2d/services/event_service.h>
#include <extra2d/services/scene_service.h>
#include <extra2d/services/timer_service.h>
#include <extra2d/services/camera_service.h>
// Application
#include <extra2d/app/application.h>

View File

@ -5,6 +5,7 @@
#include <extra2d/graphics/render_config.h>
#include <extra2d/graphics/vram_manager.h>
#include <extra2d/platform/iinput.h>
#include <extra2d/platform/input_module.h>
#include <extra2d/platform/platform_init_module.h>
#include <extra2d/platform/window_module.h>
#include <extra2d/services/scene_service.h>
@ -53,6 +54,7 @@ bool Application::init(const AppConfig& config) {
register_config_module();
register_platform_module();
register_window_module();
register_input_module();
register_render_module();
auto* configInit = ModuleRegistry::instance().getInitializer(get_config_module_id());
@ -74,6 +76,7 @@ bool Application::init(const std::string& configPath) {
register_config_module();
register_platform_module();
register_window_module();
register_input_module();
register_render_module();
auto* configInit = ModuleRegistry::instance().getInitializer(get_config_module_id());
@ -90,7 +93,7 @@ bool Application::init(const std::string& configPath) {
void Application::registerCoreServices() {
auto& locator = ServiceLocator::instance();
if (!locator.hasService<IEventService>()) {
if (!locator.hasService<ISceneService>()) {
locator.registerService<ISceneService>(makeShared<SceneService>());
}
@ -98,10 +101,6 @@ void Application::registerCoreServices() {
locator.registerService<ITimerService>(makeShared<TimerService>());
}
if (!locator.hasService<IEventService>()) {
locator.registerService<IEventService>(makeShared<EventService>());
}
if (!locator.hasService<ICameraService>()) {
auto cameraService = makeShared<CameraService>();
if (window_) {
@ -119,6 +118,12 @@ void Application::registerCoreServices() {
}
bool Application::initModules() {
auto& locator = ServiceLocator::instance();
if (!locator.hasService<IEventService>()) {
locator.registerService<IEventService>(makeShared<EventService>());
}
auto initOrder = ModuleRegistry::instance().getInitializationOrder();
for (ModuleId moduleId : initOrder) {

View File

@ -698,9 +698,15 @@ void GLRenderer::addShapeVertex(float x, float y, const Color &color) {
if (shapeVertexCount_ >= MAX_SHAPE_VERTICES) {
flushShapeBatch();
}
glm::vec4 pos(x, y, 0.0f, 1.0f);
if (!transformStack_.empty()) {
pos = transformStack_.back() * pos;
}
ShapeVertex &v = shapeVertexCache_[shapeVertexCount_++];
v.x = x;
v.y = y;
v.x = pos.x;
v.y = pos.y;
v.r = color.r;
v.g = color.g;
v.b = color.b;
@ -717,9 +723,15 @@ void GLRenderer::addLineVertex(float x, float y, const Color &color) {
if (lineVertexCount_ >= MAX_LINE_VERTICES) {
flushLineBatch();
}
glm::vec4 pos(x, y, 0.0f, 1.0f);
if (!transformStack_.empty()) {
pos = transformStack_.back() * pos;
}
ShapeVertex &v = lineVertexCache_[lineVertexCount_++];
v.x = x;
v.y = y;
v.x = pos.x;
v.y = pos.y;
v.r = color.r;
v.g = color.g;
v.b = color.b;

View File

@ -1,10 +1,13 @@
#include <extra2d/platform/input_module.h>
#include <extra2d/config/module_registry.h>
#include <extra2d/core/service_locator.h>
#include <extra2d/platform/window_module.h>
#include <extra2d/services/event_service.h>
#include <extra2d/utils/logger.h>
#include <nlohmann/json.hpp>
#include "backends/sdl2/sdl2_input.h"
using json = nlohmann::json;
namespace extra2d {
@ -146,6 +149,20 @@ bool InputModuleInitializer::initialize(const IModuleConfig* config) {
return false;
}
SDL2Input* sdl2Input = dynamic_cast<SDL2Input*>(input_);
if (sdl2Input) {
auto eventService = ServiceLocator::instance().getService<IEventService>();
if (eventService) {
sdl2Input->setEventCallback([eventService](const Event& event) {
Event mutableEvent = event;
eventService->dispatch(mutableEvent);
});
E2D_LOG_INFO("Input events connected to EventService");
} else {
E2D_LOG_WARN("EventService not available - input events will not be dispatched");
}
}
initialized_ = true;
E2D_LOG_INFO("Input module initialized");
E2D_LOG_INFO(" Deadzone: {}", config_.deadzone);

View File

@ -520,11 +520,15 @@ void Node::onRender(RenderBackend &renderer) {
if (!visible_)
return;
renderer.pushTransform(getLocalTransform());
onDraw(renderer);
for (auto &child : children_) {
child->onRender(renderer);
}
renderer.popTransform();
}
/**

View File

@ -268,25 +268,24 @@ Rect ShapeNode::getBounds() const {
* @param renderer
*
*
* Node::onRender pushTransform
* 使
*/
void ShapeNode::onDraw(RenderBackend &renderer) {
if (points_.empty()) {
return;
}
Vec2 offset = getPosition();
switch (shapeType_) {
case ShapeType::Point:
if (!points_.empty()) {
renderer.fillCircle(points_[0] + offset, lineWidth_ * 0.5f, color_, 8);
renderer.fillCircle(points_[0], lineWidth_ * 0.5f, color_, 8);
}
break;
case ShapeType::Line:
if (points_.size() >= 2) {
renderer.drawLine(points_[0] + offset, points_[1] + offset, color_,
lineWidth_);
renderer.drawLine(points_[0], points_[1], color_, lineWidth_);
}
break;
@ -295,11 +294,11 @@ void ShapeNode::onDraw(RenderBackend &renderer) {
if (filled_) {
Rect rect(points_[0].x, points_[0].y, points_[2].x - points_[0].x,
points_[2].y - points_[0].y);
renderer.fillRect(Rect(rect.origin + offset, rect.size), color_);
renderer.fillRect(rect, color_);
} else {
for (size_t i = 0; i < points_.size(); ++i) {
Vec2 start = points_[i] + offset;
Vec2 end = points_[(i + 1) % points_.size()] + offset;
Vec2 start = points_[i];
Vec2 end = points_[(i + 1) % points_.size()];
renderer.drawLine(start, end, color_, lineWidth_);
}
}
@ -310,41 +309,31 @@ void ShapeNode::onDraw(RenderBackend &renderer) {
if (points_.size() >= 2) {
float radius = points_[1].x;
if (filled_) {
renderer.fillCircle(points_[0] + offset, radius, color_, segments_);
renderer.fillCircle(points_[0], radius, color_, segments_);
} else {
renderer.drawCircle(points_[0] + offset, radius, color_, segments_,
lineWidth_);
renderer.drawCircle(points_[0], radius, color_, segments_, lineWidth_);
}
}
break;
case ShapeType::Triangle:
if (points_.size() >= 3) {
Vec2 p1 = points_[0] + offset;
Vec2 p2 = points_[1] + offset;
Vec2 p3 = points_[2] + offset;
if (filled_) {
renderer.fillTriangle(p1, p2, p3, color_);
renderer.fillTriangle(points_[0], points_[1], points_[2], color_);
} else {
renderer.drawLine(p1, p2, color_, lineWidth_);
renderer.drawLine(p2, p3, color_, lineWidth_);
renderer.drawLine(p3, p1, color_, lineWidth_);
renderer.drawLine(points_[0], points_[1], color_, lineWidth_);
renderer.drawLine(points_[1], points_[2], color_, lineWidth_);
renderer.drawLine(points_[2], points_[0], color_, lineWidth_);
}
}
break;
case ShapeType::Polygon:
if (!points_.empty()) {
std::vector<Vec2> transformedPoints;
transformedPoints.reserve(points_.size());
for (const auto &p : points_) {
transformedPoints.push_back(p + offset);
}
if (filled_) {
renderer.fillPolygon(transformedPoints, color_);
renderer.fillPolygon(points_, color_);
} else {
renderer.drawPolygon(transformedPoints, color_, lineWidth_);
renderer.drawPolygon(points_, color_, lineWidth_);
}
}
break;

File diff suppressed because it is too large Load Diff

View File

@ -1,30 +1,112 @@
/**
* @file main.cpp
* @brief Extra2D
* @brief Extra2D
*
* 使 Extra2D
*
* -
* -
* -
* -
*/
#include <extra2d/extra2d.h>
#include <extra2d/graphics/render_config.h>
#include <extra2d/platform/window_config.h>
#include <iostream>
using namespace extra2d;
/**
* @brief
*/
void createSceneGraph(Scene *scene) {
float width = scene->getWidth();
float height = scene->getHeight();
auto root = makeShared<Node>();
root->setName("Root");
root->setPos(width / 2, height / 2);
scene->addChild(root);
auto parent1 = makeShared<Node>();
parent1->setName("Parent1");
parent1->setPos(-200, 0);
root->addChild(parent1);
auto rect1 = ShapeNode::createFilledRect(Rect(-50, -50, 100, 100),
Color(1.0f, 0.4f, 0.4f, 1.0f));
rect1->setName("RedRect");
parent1->addChild(rect1);
auto child1 = makeShared<Node>();
child1->setName("Child1");
child1->setPos(80, 0);
child1->setRotation(45);
child1->setScale(0.5f);
parent1->addChild(child1);
auto smallRect = ShapeNode::createFilledRect(Rect(-30, -30, 60, 60),
Color(1.0f, 0.8f, 0.4f, 1.0f));
smallRect->setName("OrangeRect");
child1->addChild(smallRect);
auto parent2 = makeShared<Node>();
parent2->setName("Parent2");
parent2->setPos(200, 0);
root->addChild(parent2);
auto circle1 = ShapeNode::createFilledCircle(Vec2(0, 0), 60,
Color(0.4f, 0.4f, 1.0f, 1.0f));
circle1->setName("BlueCircle");
parent2->addChild(circle1);
auto child2 = makeShared<Node>();
child2->setName("Child2");
child2->setPos(0, 100);
parent2->addChild(child2);
auto triangle = ShapeNode::createFilledTriangle(
Vec2(0, -40), Vec2(-35, 30), Vec2(35, 30), Color(0.4f, 1.0f, 0.4f, 1.0f));
triangle->setName("GreenTriangle");
child2->addChild(triangle);
auto line = ShapeNode::createLine(Vec2(-300, -200), Vec2(300, -200),
Color(1.0f, 1.0f, 1.0f, 1.0f), 2.0f);
line->setName("BottomLine");
root->addChild(line);
auto polygon = ShapeNode::createFilledPolygon(
{Vec2(0, -50), Vec2(50, 0), Vec2(30, 50), Vec2(-30, 50), Vec2(-50, 0)},
Color(1.0f, 0.4f, 1.0f, 1.0f));
polygon->setName("PurplePolygon");
polygon->setPos(0, -150);
root->addChild(polygon);
std::cout << "\n=== Scene Graph Structure ===" << std::endl;
std::cout << "Scene (root)" << std::endl;
std::cout << " └── Root (center)" << std::endl;
std::cout << " ├── Parent1 (left)" << std::endl;
std::cout << " │ ├── RedRect (100x100)" << std::endl;
std::cout << " │ └── Child1 (rotated 45°, scaled 0.5)" << std::endl;
std::cout << " │ └── OrangeRect (60x60)" << std::endl;
std::cout << " ├── Parent2 (right)" << std::endl;
std::cout << " │ ├── BlueCircle (radius 60)" << std::endl;
std::cout << " │ └── Child2 (below)" << std::endl;
std::cout << " │ └── GreenTriangle" << std::endl;
std::cout << " ├── BottomLine" << std::endl;
std::cout << " └── PurplePolygon (pentagon)" << std::endl;
std::cout << "=============================\n" << std::endl;
}
/**
* @brief
*
*
*/
int main(int argc, char *argv[]) {
(void)argc;
(void)argv;
std::cout << "Extra2D Demo - Starting..." << std::endl;
std::cout << "Extra2D Scene Graph Demo - Starting..." << std::endl;
AppConfig config = AppConfig::createDefault();
config.appName = "Extra2D Demo";
config.appName = "Extra2D Scene Graph Demo";
config.appVersion = "1.0.0";
Application &app = Application::get();
@ -37,15 +119,40 @@ int main(int argc, char *argv[]) {
std::cout << "Application initialized successfully!" << std::endl;
std::cout << "Window: " << app.window().width() << "x"
<< app.window().height() << std::endl;
std::cout << "Running main loop. Press ESC or close window to exit."
<< std::endl;
auto eventService = app.events();
if (eventService) {
eventService->addListener(EventType::KeyPressed, [](Event &e) {
auto &keyEvent = std::get<KeyEvent>(e.data);
if (keyEvent.keyCode == static_cast<int>(Key::Escape)) {
e.handled = true;
Application::get().quit();
}
});
eventService->addListener(EventType::MouseButtonPressed, [](Event &e) {
auto &mouseEvent = std::get<MouseButtonEvent>(e.data);
std::cout << "[Click] Button " << mouseEvent.button << " at ("
<< mouseEvent.position.x << ", " << mouseEvent.position.y << ")"
<< std::endl;
});
}
auto scene = Scene::create();
scene->setBackgroundColor(Colors::SkyBlue);
scene->setBackgroundColor(Color(0.12f, 0.12f, 0.16f, 1.0f));
scene->setViewportSize(static_cast<float>(app.window().width()),
static_cast<float>(app.window().height()));
createSceneGraph(scene.get());
app.enterScene(scene);
std::cout << "\nControls:" << std::endl;
std::cout << " ESC - Exit" << std::endl;
std::cout << " Mouse Click - Print position" << std::endl;
std::cout << "\nRunning main loop...\n" << std::endl;
app.run();
std::cout << "Shutting down..." << std::endl;