diff --git a/examples/collision_demo/main.cpp b/examples/collision_demo/main.cpp deleted file mode 100644 index 2c1b0b4..0000000 --- a/examples/collision_demo/main.cpp +++ /dev/null @@ -1,280 +0,0 @@ -#include -#include - -using namespace extra2d; - -// ============================================================================ -// 碰撞测试节点 - 有实际边界框 -// ============================================================================ -class CollisionBox : public Node { -public: - CollisionBox(float width, float height, const Color &color) - : width_(width), height_(height), color_(color), isColliding_(false) { - // 启用空间索引,这是碰撞检测的关键 - setSpatialIndexed(true); - } - - void setColliding(bool colliding) { isColliding_ = colliding; } - - Rect boundingBox() const override { - // 返回实际的矩形边界 - Vec2 position = pos(); - return Rect(position.x - width_ / 2, position.y - height_ / 2, width_, - height_); - } - - void onRender(Renderer &renderer) override { - Vec2 position = pos(); - - // 绘制填充矩形 - Color fillColor = isColliding_ ? Color(1.0f, 0.2f, 0.2f, 0.8f) : color_; - renderer.fillRect(Rect(position.x - width_ / 2, position.y - height_ / 2, - width_, height_), - fillColor); - - // 绘制边框 - Color borderColor = isColliding_ ? Color(1.0f, 0.0f, 0.0f, 1.0f) - : Color(1.0f, 1.0f, 1.0f, 0.5f); - float borderWidth = isColliding_ ? 3.0f : 2.0f; - renderer.drawRect(Rect(position.x - width_ / 2, position.y - height_ / 2, - width_, height_), - borderColor, borderWidth); - } - -private: - float width_, height_; - Color color_; - bool isColliding_; -}; - -// ============================================================================ -// 碰撞检测场景 -// ============================================================================ -class CollisionDemoScene : public Scene { -public: - void onEnter() override { - E2D_LOG_INFO("CollisionDemoScene::onEnter - 碰撞检测演示"); - - // 设置背景色 - setBackgroundColor(Color(0.05f, 0.05f, 0.1f, 1.0f)); - - // 获取屏幕中心 - auto &app = Application::instance(); - float centerX = app.getConfig().width / 2.0f; - float centerY = app.getConfig().height / 2.0f; - - // 创建静态碰撞框 - createStaticBoxes(centerX, centerY); - - // 创建移动的中心方块 - centerBox_ = - shared(80.0f, 80.0f, Color(0.2f, 0.6f, 1.0f, 0.8f)); - centerBox_->setPosition(Vec2(centerX, centerY)); - addChild(centerBox_); - - // 加载字体并创建UI - loadFonts(); - - E2D_LOG_INFO("创建了 {} 个碰撞框", boxes_.size() + 1); - } - - void onUpdate(float dt) override { - Scene::onUpdate(dt); - - // 旋转中心方块 - rotationAngle_ += rotationSpeed_ * dt; - if (rotationAngle_ >= 360.0f) - rotationAngle_ -= 360.0f; - - // 让中心方块沿圆形路径移动 - float radius = 150.0f; - float rad = rotationAngle_ * 3.14159f / 180.0f; - - auto &app = Application::instance(); - Vec2 center = - Vec2(app.getConfig().width / 2.0f, app.getConfig().height / 2.0f); - - centerBox_->setPosition(Vec2(center.x + std::cos(rad) * radius, - center.y + std::sin(rad) * radius)); - centerBox_->setRotation(rotationAngle_); - - // 执行碰撞检测 - performCollisionDetection(); - - // 更新UI文本 - updateUI(); - - // 检查退出按键 - auto &input = Application::instance().input(); - if (input.isButtonPressed(GamepadButton::Start)) { - E2D_LOG_INFO("退出应用"); - Application::instance().quit(); - } - } - -private: - /** - * @brief 加载字体资源并创建UI文本 - */ - void loadFonts() { - auto &resources = Application::instance().resources(); - titleFont_ = resources.loadFont("assets/font.ttf", 60, true); - infoFont_ = resources.loadFont("assets/font.ttf", 28, true); - - // 创建标题文本 - titleText_ = Text::create("碰撞检测演示", titleFont_); - titleText_->setPosition(50.0f, 30.0f); - titleText_->setTextColor(Color(1.0f, 1.0f, 1.0f, 1.0f)); - addChild(titleText_); - - // 创建说明文本 - descText_ = Text::create("蓝色方块旋转并检测碰撞", infoFont_); - descText_->setPosition(50.0f, 80.0f); - descText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f)); - addChild(descText_); - - collideHintText_ = Text::create("红色 = 检测到碰撞", infoFont_); - collideHintText_->setPosition(50.0f, 105.0f); - collideHintText_->setTextColor(Color(1.0f, 0.5f, 0.5f, 1.0f)); - addChild(collideHintText_); - - // 创建动态统计文本 - collisionText_ = Text::create("", infoFont_); - collisionText_->setPosition(50.0f, 150.0f); - collisionText_->setTextColor(Color(1.0f, 1.0f, 0.5f, 1.0f)); - addChild(collisionText_); - - fpsText_ = Text::create("", infoFont_); - fpsText_->setPosition(50.0f, 175.0f); - fpsText_->setTextColor(Color(0.8f, 1.0f, 0.8f, 1.0f)); - addChild(fpsText_); - - // 创建退出提示文本 - float screenHeight = - static_cast(Application::instance().getConfig().height); - exitHintText_ = Text::create("按 + 键退出", infoFont_); - exitHintText_->setPosition(50.0f, screenHeight - 50.0f); - exitHintText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f)); - addChild(exitHintText_); - } - - /** - * @brief 更新UI文本 - */ - void updateUI() { - auto &app = Application::instance(); - - // 使用 setFormat 更新动态文本 - collisionText_->setFormat("碰撞数: %zu", collisionCount_); - fpsText_->setFormat("FPS: %u", app.fps()); - } - - /** - * @brief 创建静态碰撞框 - */ - void createStaticBoxes(float centerX, float centerY) { - // 创建围绕中心的静态碰撞框 - std::vector> positions = { - {Vec2(centerX - 200, centerY - 150), Color(0.3f, 1.0f, 0.3f, 0.7f)}, - {Vec2(centerX + 200, centerY - 150), Color(1.0f, 0.3f, 0.3f, 0.7f)}, - {Vec2(centerX - 200, centerY + 150), Color(0.3f, 0.3f, 1.0f, 0.7f)}, - {Vec2(centerX + 200, centerY + 150), Color(1.0f, 1.0f, 0.3f, 0.7f)}, - {Vec2(centerX, centerY - 220), Color(1.0f, 0.3f, 1.0f, 0.7f)}, - {Vec2(centerX, centerY + 220), Color(0.3f, 1.0f, 1.0f, 0.7f)}, - }; - - for (const auto &[pos, color] : positions) { - auto box = shared(70.0f, 70.0f, color); - box->setPosition(pos); - addChild(box); - boxes_.push_back(box); - } - } - - /** - * @brief 执行碰撞检测 - */ - void performCollisionDetection() { - // 清除之前的碰撞状态 - centerBox_->setColliding(false); - for (auto &box : boxes_) { - box->setColliding(false); - } - - // 使用空间索引进行碰撞检测 - auto collisions = queryCollisions(); - - collisionCount_ = collisions.size(); - - // 标记碰撞的节点 - for (const auto &[nodeA, nodeB] : collisions) { - if (auto boxA = dynamic_cast(nodeA)) { - boxA->setColliding(true); - } - if (auto boxB = dynamic_cast(nodeB)) { - boxB->setColliding(true); - } - } - } - - Ptr centerBox_; - std::vector> boxes_; - float rotationAngle_ = 0.0f; - float rotationSpeed_ = 60.0f; // 旋转速度(度/秒) - size_t collisionCount_ = 0; - - // 字体资源 - Ptr titleFont_; - Ptr infoFont_; - - // UI 文本组件 - Ptr titleText_; - Ptr descText_; - Ptr collideHintText_; - Ptr collisionText_; - Ptr fpsText_; - Ptr exitHintText_; -}; - -// ============================================================================ -// 程序入口 -// ============================================================================ - -int main(int argc, char **argv) { - // 初始化日志系统 - Logger::init(); - Logger::setLevel(LogLevel::Debug); - - E2D_LOG_INFO("========================"); - E2D_LOG_INFO("Easy2D 碰撞检测演示"); - E2D_LOG_INFO("========================"); - - // 获取应用实例 - auto &app = Application::instance(); - - // 配置应用 - AppConfig config; - config.title = "Easy2D - 碰撞检测演示"; - config.width = 1280; - config.height = 720; - config.vsync = true; - config.fpsLimit = 60; - - // 初始化应用 - if (!app.init(config)) { - E2D_LOG_ERROR("应用初始化失败!"); - return -1; - } - - // 进入场景 - app.enterScene(shared()); - - E2D_LOG_INFO("开始主循环..."); - - // 运行应用 - app.run(); - - E2D_LOG_INFO("应用结束"); - - return 0; -} diff --git a/examples/collision_demo/romfs/assets/font.ttf b/examples/collision_demo/romfs/assets/font.ttf deleted file mode 100644 index 8997148..0000000 Binary files a/examples/collision_demo/romfs/assets/font.ttf and /dev/null differ diff --git a/examples/collision_demo/xmake.lua b/examples/collision_demo/xmake.lua deleted file mode 100644 index 284abc0..0000000 --- a/examples/collision_demo/xmake.lua +++ /dev/null @@ -1,76 +0,0 @@ --- ============================================== --- Collision Demo 示例 - Xmake 构建脚本 --- 支持平台: MinGW (Windows), Nintendo Switch --- ============================================== - --- 获取当前脚本所在目录(示例根目录) -local example_dir = os.scriptdir() - --- 可执行文件目标 -target("collision_demo") - set_kind("binary") - add_files("main.cpp") - add_includedirs("../../include") - add_deps("extra2d") - - -- 使用与主项目相同的平台配置 - if is_plat("switch") then - set_targetdir("../../build/examples/collision_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, "collision_demo.nacp") - local nro_file = path.join(output_dir, "collision_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", "Collision 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(), "collision_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/collision_demo") - add_ldflags("-mwindows", {force = true}) - - -- 复制资源到输出目录 - after_build(function (target) - local romfs = path.join(example_dir, "romfs") - if os.isdir(romfs) then - local target_dir = path.directory(target:targetfile()) - 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) - else - print("Warning: romfs directory not found at " .. romfs) - end - end) - end -target_end() diff --git a/examples/hello_world/main.cpp b/examples/hello_world/main.cpp index c8fd8a7..f08d3e5 100644 --- a/examples/hello_world/main.cpp +++ b/examples/hello_world/main.cpp @@ -1,124 +1,27 @@ #include +#include using namespace extra2d; -// ============================================================================ -// Hello World 场景 -// ============================================================================ - -/** - * @brief Hello World 场景类 - * 显示简单的 "Hello World" 文字 - */ -class HelloWorldScene : public Scene { -public: - /** - * @brief 场景进入时调用 - */ - void onEnter() override { - E2D_LOG_INFO("HelloWorldScene::onEnter - 进入场景"); - - // 设置背景颜色为深蓝色 - setBackgroundColor(Color(0.1f, 0.1f, 0.3f, 1.0f)); - - // 加载字体(支持多种字体后备) - auto &resources = Application::instance().resources(); - font_ = resources.loadFont("assets/font.ttf", 48, true); - - if (!font_) { - E2D_LOG_ERROR("字体加载失败,文字渲染将不可用!"); - return; - } - - // 创建 "你好世界" 文本组件 - 使用节点位置 - auto text1 = Text::create("你好世界", font_); - text1->setPosition(640.0f, 360.0f); // 屏幕中心 - text1->setAnchor(0.5f, 0.5f); // 中心锚点,让文字中心对准位置 - text1->setTextColor(Color(1.0f, 1.0f, 1.0f, 1.0f)); - addChild(text1); - - // 创建提示文本组件 - 固定在屏幕底部 - auto text2 = Text::create("退出按键(START 按钮)", font_); - text2->setPosition(640.0f, 650.0f); // 屏幕底部 - text2->setAnchor(0.5f, 0.5f); - text2->setTextColor(Color(1.0f, 1.0f, 0.0f, 1.0f)); - addChild(text2); - - // 创建带偏移的文本 - 使用相对位置 - auto text3 = Text::create("偏移文本", font_); - text3->setPosition(50.0f, 50.0f); // 左上角偏移 - text3->setAnchor(0.0f, 0.0f); // 左上角锚点,文字从指定位置开始显示 - text3->setTextColor(Color(0.0f, 1.0f, 1.0f, 1.0f)); - addChild(text3); - - // 创建世界空间文本 - 随相机移动(默认行为) - auto text4 = Text::create("世界空间文本", font_); - text4->setPosition(100.0f, 100.0f); // 世界坐标 - text4->setAnchor(0.0f, 0.0f); // 左上角锚点,文字从指定位置开始显示 - text4->setTextColor(Color(1.0f, 0.5f, 0.5f, 1.0f)); - addChild(text4); - } - - /** - * @brief 每帧更新时调用 - * @param dt 时间间隔(秒) - */ - void onUpdate(float dt) override { - Scene::onUpdate(dt); - - // 检查退出按键 - auto &input = Application::instance().input(); - - // 使用手柄 START 按钮退出 (GamepadButton::Start) - if (input.isButtonPressed(GamepadButton::Start)) { - E2D_LOG_INFO("退出应用 (START 按钮)"); - Application::instance().quit(); - } - } - -private: - Ptr font_; // 字体图集 -}; - -// ============================================================================ -// 程序入口 -// ============================================================================ - int main(int argc, char **argv) { - // 初始化日志系统 - Logger::init(); - Logger::setLevel(LogLevel::Debug); - - E2D_LOG_INFO("========================"); - E2D_LOG_INFO("Easy2D Hello World Demo"); - E2D_LOG_INFO("========================"); - - // 获取应用实例 - auto &app = Application::instance(); - - // 配置应用 + // 创建应用(自动创建窗口) + auto app = Application::create(); + AppConfig config; - config.title = "Easy2D - Hello World"; + config.title = "Hello World"; config.width = 1280; config.height = 720; - config.vsync = true; - config.fpsLimit = 60; - - // 初始化应用 - if (!app.init(config)) { - E2D_LOG_ERROR("应用初始化失败!"); + + if (!app->init(config)) { + printf("Failed to initialize!\n"); return -1; } - - // 进入 Hello World 场景 - app.enterScene(shared()); - - E2D_LOG_INFO("开始主循环..."); - + + printf("Window: %dx%d\n", app->getWindowWidth(), app->getWindowHeight()); + // 运行应用 - app.run(); - - E2D_LOG_INFO("应用结束"); - + app->run(); + + // 自动销毁 return 0; } diff --git a/examples/hello_world/xmake.lua b/examples/hello_world/xmake.lua index d21e025..b2b074a 100644 --- a/examples/hello_world/xmake.lua +++ b/examples/hello_world/xmake.lua @@ -51,7 +51,8 @@ target("hello_world") elseif is_plat("mingw") then set_targetdir("../../build/examples/hello_world") - add_ldflags("-mwindows", {force = true}) + -- 使用控制台子系统以便查看输出 + -- add_ldflags("-mwindows", {force = true}) -- 复制资源到输出目录 after_build(function (target) diff --git a/include/app/application.h b/include/app/application.h index eab9cf1..baaf8fc 100644 --- a/include/app/application.h +++ b/include/app/application.h @@ -8,6 +8,8 @@ namespace extra2d { // 前向声明 class Context; +class WindowModule; +class InputModule; /** * @brief 应用程序配置 @@ -112,6 +114,31 @@ public: */ Context* getContext() const { return context_.get(); } + /** + * @brief 获取窗口模块 + */ + WindowModule* getWindow() const { return windowModule_.get(); } + + /** + * @brief 获取输入模块 + */ + InputModule* getInput() const { return inputModule_.get(); } + + /** + * @brief 获取窗口宽度 + */ + int32 getWindowWidth() const; + + /** + * @brief 获取窗口高度 + */ + int32 getWindowHeight() const; + + /** + * @brief 获取窗口标题 + */ + const char* getWindowTitle() const; + private: Application(); @@ -119,6 +146,8 @@ private: void update(); std::unique_ptr context_; + std::unique_ptr windowModule_; + std::unique_ptr inputModule_; AppConfig config_; diff --git a/src/app/application.cpp b/src/app/application.cpp index 73c1cd1..5543ce5 100644 --- a/src/app/application.cpp +++ b/src/app/application.cpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include #include @@ -84,6 +86,27 @@ bool Application::init(const AppConfig &config) { return false; } + // 创建窗口模块 + windowModule_ = std::make_unique(); + WindowCfg wcfg; + wcfg.title = config_.title; + wcfg.width = config_.width; + wcfg.height = config_.height; + wcfg.fullscreen = config_.fullscreen; + wcfg.resizable = config_.resizable; + wcfg.vsync = config_.vsync; + wcfg.glMajor = config_.glMajor; + wcfg.glMinor = config_.glMinor; + + if (!windowModule_->create(wcfg)) { + E2D_LOG_ERROR("Failed to create window"); + return false; + } + windowModule_->setVisible(true); + + // 创建输入模块 + inputModule_ = std::make_unique(); + initialized_ = true; running_ = true; @@ -103,7 +126,11 @@ void Application::shutdown() { E2D_LOG_INFO("Shutting down application..."); - // 关闭上下文(会自动关闭模块和插件) + // 智能指针自动销毁窗口和输入模块 + inputModule_.reset(); + windowModule_.reset(); + + // 关闭上下文 context_.reset(); Sdl2::shutdown(); @@ -155,6 +182,15 @@ void Application::resume() { } void Application::mainLoop() { + // 处理窗口事件 + if (windowModule_) { + if (!windowModule_->pollEvents()) { + // 窗口关闭事件 + quit(); + return; + } + } + double currentTime = getTimeSeconds(); deltaTime_ = static_cast(currentTime - lastFrameTime_); lastFrameTime_ = currentTime; @@ -173,6 +209,11 @@ void Application::mainLoop() { update(); } + // 交换缓冲区 + if (windowModule_) { + windowModule_->swapBuffers(); + } + // 帧率限制 if (!config_.vsync && config_.fpsLimit > 0) { double frameEndTime = getTimeSeconds(); @@ -192,4 +233,24 @@ void Application::update() { } } +int32 Application::getWindowWidth() const { + if (windowModule_) { + Size size = windowModule_->getSize(); + return static_cast(size.w); + } + return config_.width; +} + +int32 Application::getWindowHeight() const { + if (windowModule_) { + Size size = windowModule_->getSize(); + return static_cast(size.h); + } + return config_.height; +} + +const char* Application::getWindowTitle() const { + return config_.title.c_str(); +} + } // namespace extra2d diff --git a/xmake.lua b/xmake.lua index 7f9402d..0c159db 100644 --- a/xmake.lua +++ b/xmake.lua @@ -91,11 +91,10 @@ includes("xmake/engine.lua") -- 定义引擎库 define_extra2d_engine() --- -- 示例程序目标(作为子项目) --- if is_config("examples","true") then --- includes("examples/hello_world", {rootdir = "examples/hello_world"}) --- includes("examples/collision_demo", {rootdir = "examples/collision_demo"}) --- end +-- 示例程序目标(作为子项目) +if is_config("examples","true") then + includes("examples/hello_world", {rootdir = "examples/hello_world"}) +end -- 测试套件目标(作为子项目) if is_config("tests","true") then