diff --git a/examples/flappy_bird/BaseScene.cpp b/examples/flappy_bird/BaseScene.cpp deleted file mode 100644 index 2f9dfdb..0000000 --- a/examples/flappy_bird/BaseScene.cpp +++ /dev/null @@ -1,127 +0,0 @@ -// ============================================================================ -// BaseScene.cpp - Flappy Bird 基础场景实现 -// ============================================================================ - -#include "BaseScene.h" -#include -#include - -namespace flappybird { - -BaseScene::BaseScene() { - // 设置背景颜色为黑色(窗口四周会显示这个颜色) - setBackgroundColor(extra2d::Color(0.0f, 0.0f, 0.0f, 1.0f)); -} - -void BaseScene::onEnter() { - extra2d::Scene::onEnter(); - // 计算并更新视口 - updateViewport(); -} - -void BaseScene::updateViewport() { - auto &app = extra2d::Application::instance(); - float windowWidth = static_cast(app.window().width()); - float windowHeight = static_cast(app.window().height()); - - // 计算游戏内容在窗口中的居中位置 - // 保持游戏原始宽高比,进行"黑边"适配 - float scaleX = windowWidth / GAME_WIDTH; - float scaleY = windowHeight / GAME_HEIGHT; - // 使用较小的缩放比例,确保游戏内容完整显示在窗口中 - float scale = std::min(scaleX, scaleY); - - scaledGameWidth_ = GAME_WIDTH * scale; - scaledGameHeight_ = GAME_HEIGHT * scale; - // 计算居中偏移,使游戏内容在窗口中水平和垂直居中 - viewportOffsetX_ = (windowWidth - scaledGameWidth_) * 0.5f; - viewportOffsetY_ = (windowHeight - scaledGameHeight_) * 0.5f; - - // 设置视口大小为游戏逻辑分辨率 - setViewportSize(GAME_WIDTH, GAME_HEIGHT); - - // 创建并设置相机 - auto camera = extra2d::makePtr(); - // 设置正交投影,覆盖整个游戏逻辑区域 - // 注意:对于2D游戏,Y轴向下增长,所以bottom > top - camera->setViewport(0.0f, GAME_WIDTH, GAME_HEIGHT, 0.0f); - setCamera(camera); -} - -void BaseScene::onRender(extra2d::Renderer &renderer) { - // 检查窗口大小是否改变,如果改变则更新视口 - auto &app = extra2d::Application::instance(); - float currentWindowWidth = static_cast(app.window().width()); - float currentWindowHeight = static_cast(app.window().height()); - - // 如果窗口大小改变,重新计算视口 - float expectedWidth = scaledGameWidth_ + viewportOffsetX_ * 2.0f; - float expectedHeight = scaledGameHeight_ + viewportOffsetY_ * 2.0f; - if (std::abs(currentWindowWidth - expectedWidth) > 1.0f || - std::abs(currentWindowHeight - expectedHeight) > 1.0f) { - E2D_LOG_INFO("BaseScene::onRender - window size changed from ({} x {}) to " - "({} x {}), updating viewport", - expectedWidth, expectedHeight, currentWindowWidth, - currentWindowHeight); - updateViewport(); - } - - // 设置视口为居中区域 - renderer.setViewport( - static_cast(viewportOffsetX_), static_cast(viewportOffsetY_), - static_cast(scaledGameWidth_), static_cast(scaledGameHeight_)); - - // 调用父类的 onRender 进行实际渲染 - extra2d::Scene::onRender(renderer); -} - -void BaseScene::renderContent(extra2d::Renderer &renderer) { - // 如果视口参数未初始化(onEnter 还没被调用),先初始化 - if (scaledGameWidth_ <= 0.0f || scaledGameHeight_ <= 0.0f) { - updateViewport(); - } - - // 检查窗口大小是否改变 - auto &app = extra2d::Application::instance(); - float currentWindowWidth = static_cast(app.window().width()); - float currentWindowHeight = static_cast(app.window().height()); - - float expectedWidth = scaledGameWidth_ + viewportOffsetX_ * 2.0f; - float expectedHeight = scaledGameHeight_ + viewportOffsetY_ * 2.0f; - if (std::abs(currentWindowWidth - expectedWidth) > 1.0f || - std::abs(currentWindowHeight - expectedHeight) > 1.0f) { - updateViewport(); - } - - // 检查当前场景是否作为 TransitionScene 的子场景被渲染 - bool isChildOfTransition = false; - if (auto parentNode = parent()) { - if (dynamic_cast(parentNode.get())) { - isChildOfTransition = true; - } - } - - if (isChildOfTransition) { - // 作为 TransitionScene 的子场景时,需要设置正确的投影矩阵 - // 使用游戏逻辑分辨率作为投影区域,让 TransitionScene 控制整体视口 - auto camera = getActiveCamera(); - if (camera) { - // 设置投影矩阵覆盖整个游戏逻辑区域 - renderer.setViewProjection(camera->getViewProjectionMatrix()); - } - // 渲染场景内容(投影矩阵已设置,直接渲染) - batchUpdateTransforms(); - renderer.beginSpriteBatch(); - render(renderer); - renderer.endSpriteBatch(); - } else { - // 正常渲染时,调用父类的 renderContent 处理视口和投影 - renderer.setViewport(static_cast(viewportOffsetX_), - static_cast(viewportOffsetY_), - static_cast(scaledGameWidth_), - static_cast(scaledGameHeight_)); - extra2d::Scene::renderContent(renderer); - } -} - -} // namespace flappybird diff --git a/examples/flappy_bird/BaseScene.h b/examples/flappy_bird/BaseScene.h deleted file mode 100644 index 31689e8..0000000 --- a/examples/flappy_bird/BaseScene.h +++ /dev/null @@ -1,57 +0,0 @@ -// ============================================================================ -// BaseScene.h - Flappy Bird 基础场景类 -// 描述: 提供统一的居中视口适配功能,所有游戏场景都应继承此类 -// ============================================================================ - -#pragma once - -#include - -namespace flappybird { - -// 游戏逻辑分辨率(原始 Flappy Bird 尺寸) -static constexpr float GAME_WIDTH = 288.0f; -static constexpr float GAME_HEIGHT = 512.0f; - -/** - * @brief Flappy Bird 基础场景类 - * 所有游戏场景都应继承此类,以获得统一的居中视口适配功能 - */ -class BaseScene : public extra2d::Scene { -public: - /** - * @brief 构造函数 - */ - BaseScene(); - - /** - * @brief 场景进入时调用 - */ - void onEnter() override; - - /** - * @brief 渲染时调用,设置居中视口 - * @param renderer 渲染后端 - */ - void onRender(extra2d::Renderer &renderer) override; - - /** - * @brief 渲染场景内容,确保视口正确设置 - * @param renderer 渲染后端 - */ - void renderContent(extra2d::Renderer &renderer) override; - -protected: - /** - * @brief 更新视口计算,使游戏内容在窗口中居中显示 - */ - void updateViewport(); - - // 视口适配参数(用于在窗口中居中显示游戏内容) - float scaledGameWidth_ = 0.0f; // 缩放后的游戏宽度 - float scaledGameHeight_ = 0.0f; // 缩放后的游戏高度 - float viewportOffsetX_ = 0.0f; // 视口水平偏移 - float viewportOffsetY_ = 0.0f; // 视口垂直偏移 -}; - -} // namespace flappybird diff --git a/examples/flappy_bird/GameOverLayer.cpp b/examples/flappy_bird/GameOverLayer.cpp deleted file mode 100644 index dcac7d6..0000000 --- a/examples/flappy_bird/GameOverLayer.cpp +++ /dev/null @@ -1,209 +0,0 @@ -// ============================================================================ -// GameOverLayer.cpp - 游戏结束层实现 -// ============================================================================ - -#include "GameOverLayer.h" -#include "BaseScene.h" -#include "GameScene.h" -#include "Number.h" -#include "ResLoader.h" -#include "StartScene.h" -#include - -namespace flappybird { - -GameOverLayer::GameOverLayer(int score) : score_(score) { - // 注意:不要在构造函数中创建子节点 - // 因为此时 weak_from_this() 还不能使用 -} - -void GameOverLayer::onEnter() { - Node::onEnter(); - - // 在 onEnter 中初始化,此时 weak_from_this() 可用 - // 使用游戏逻辑分辨率 - float screenWidth = GAME_WIDTH; - float screenHeight = GAME_HEIGHT; - - // 整体居中(x 坐标相对于屏幕中心) - // 初始位置在屏幕底部,然后向上滑出 - setPosition(extra2d::Vec2(screenWidth / 2.0f, screenHeight + 200.0f)); - - // 显示 "Game Over" 文字(y=120,从顶部开始) - auto gameOverFrame = ResLoader::getKeyFrame("text_game_over"); - if (gameOverFrame) { - auto gameOver = extra2d::Sprite::create(gameOverFrame->getTexture(), - gameOverFrame->getRect()); - gameOver->setAnchor(extra2d::Vec2(0.5f, 0.0f)); - gameOver->setPosition(extra2d::Vec2(0.0f, 120.0f)); // x=0 表示相对于中心点 - addChild(gameOver); - } - - // 初始化得分面板 - initPanel(score_, screenHeight); - - // 初始化按钮 - initButtons(); - - // 向上滑出动画 - extra2d::TweenOptions opts; - opts.easing = extra2d::TweenEasing::BackOut; - tween() - .to(0.5f, - extra2d::tween::pos(screenWidth / 2.0f, screenHeight / 2.0f - 300.0f), - opts) - .call([this]() { - animationDone_ = true; - if (restartBtn_) - restartBtn_->setEnabled(true); - if (menuBtn_) - menuBtn_->setEnabled(true); - if (shareBtn_) - shareBtn_->setEnabled(true); - }) - .start(); -} - -void GameOverLayer::initPanel(int score, float screenHeight) { - // 显示得分板(在屏幕中间) - auto panelFrame = ResLoader::getKeyFrame("score_panel"); - if (!panelFrame) - return; - - auto panel = - extra2d::Sprite::create(panelFrame->getTexture(), panelFrame->getRect()); - panel->setAnchor(extra2d::Vec2(0.5f, 0.5f)); - panel->setPosition( - extra2d::Vec2(0.0f, screenHeight / 2.0f)); // x=0 表示相对于中心点 - addChild(panel); - - // 获取最高分(从存储中读取) - static int bestScore = 0; - if (score > bestScore) { - bestScore = score; - } - - // 显示 "New" 标记(如果破了记录) - if (score >= bestScore && score > 0) { - auto newFrame = ResLoader::getKeyFrame("new"); - if (newFrame) { - auto newSprite = - extra2d::Sprite::create(newFrame->getTexture(), newFrame->getRect()); - newSprite->setAnchor(extra2d::Vec2(0.5f, 0.5f)); - // 调整位置使其在面板内部,靠近 BEST 分数 - newSprite->setPosition( - extra2d::Vec2(30.0f, 25.0f)); // 相对于面板的坐标,在 BEST 右侧 - panel->addChild(newSprite); - } - } - - // 显示奖牌 - auto medalFrame = getMedal(score); - if (medalFrame) { - auto medal = extra2d::Sprite::create(medalFrame->getTexture(), - medalFrame->getRect()); - medal->setAnchor(extra2d::Vec2(0.5f, 0.5f)); - medal->setPosition(extra2d::Vec2(54.0f, 68.0f)); // 相对于面板的坐标 - panel->addChild(medal); - } - - // 显示本局得分 - auto scoreNumber = extra2d::makePtr(); - scoreNumber->setLittleNumber(score); - scoreNumber->setPosition( - extra2d::Vec2(80.0f, -15.0f)); // 相对于面板的坐标,右侧对齐 - panel->addChild(scoreNumber); - - // 显示最高分 - auto bestNumber = extra2d::makePtr(); - bestNumber->setLittleNumber(bestScore); - bestNumber->setPosition( - extra2d::Vec2(80.0f, 25.0f)); // 相对于面板的坐标,右侧对齐 - panel->addChild(bestNumber); -} - -void GameOverLayer::initButtons() { - auto restartFrame = ResLoader::getKeyFrame("button_restart"); - if (restartFrame) { - restartBtn_ = extra2d::Button::create(); - restartBtn_->setBackgroundImage(restartFrame->getTexture(), - restartFrame->getRect()); - restartBtn_->setAnchor(extra2d::Vec2(0.5f, 0.5f)); - restartBtn_->setPosition(extra2d::Vec2(0.0f, 360.0f)); - restartBtn_->setEnabled(false); - restartBtn_->setOnClick([]() { - ResLoader::playMusic(MusicType::Click); - auto &app = extra2d::Application::instance(); - app.scenes().replaceScene(extra2d::makePtr(), - extra2d::TransitionType::Fade, 0.5f); - }); - addChild(restartBtn_); - } - - auto menuFrame = ResLoader::getKeyFrame("button_menu"); - if (menuFrame) { - menuBtn_ = extra2d::Button::create(); - menuBtn_->setBackgroundImage(menuFrame->getTexture(), menuFrame->getRect()); - menuBtn_->setAnchor(extra2d::Vec2(0.5f, 0.5f)); - menuBtn_->setPosition(extra2d::Vec2(0.0f, 420.0f)); - menuBtn_->setEnabled(false); - menuBtn_->setOnClick([]() { - ResLoader::playMusic(MusicType::Click); - auto &app = extra2d::Application::instance(); - app.scenes().replaceScene(extra2d::makePtr(), - extra2d::TransitionType::Fade, 0.5f); - }); - addChild(menuBtn_); - } - - auto shareFrame = ResLoader::getKeyFrame("button_share"); - if (shareFrame) { - shareBtn_ = extra2d::Button::create(); - shareBtn_->setBackgroundImage(shareFrame->getTexture(), - shareFrame->getRect()); - shareBtn_->setAnchor(extra2d::Vec2(0.5f, 0.5f)); - shareBtn_->setPosition(extra2d::Vec2(0.0f, 460.0f)); - shareBtn_->setEnabled(false); - shareBtn_->setOnClick([]() { ResLoader::playMusic(MusicType::Click); }); - addChild(shareBtn_); - } -} - -void GameOverLayer::onUpdate(float dt) { - Node::onUpdate(dt); - - if (!animationDone_) - return; - - auto &input = extra2d::Application::instance().input(); - - if (input.isButtonPressed(extra2d::GamepadButton::A)) { - ResLoader::playMusic(MusicType::Click); - auto &app = extra2d::Application::instance(); - app.scenes().replaceScene(extra2d::makePtr(), - extra2d::TransitionType::Fade, 0.5f); - } - - if (input.isButtonPressed(extra2d::GamepadButton::B)) { - ResLoader::playMusic(MusicType::Click); - auto &app = extra2d::Application::instance(); - app.scenes().replaceScene(extra2d::makePtr(), - extra2d::TransitionType::Fade, 0.5f); - } -} - -extra2d::Ptr GameOverLayer::getMedal(int score) { - if (score < 10) { - return nullptr; // 无奖牌 - } else if (score < 20) { - return ResLoader::getKeyFrame("medals_0"); // 铜牌 - } else if (score < 30) { - return ResLoader::getKeyFrame("medals_1"); // 银牌 - } else if (score < 50) { - return ResLoader::getKeyFrame("medals_2"); // 金牌 - } else { - return ResLoader::getKeyFrame("medals_3"); // 钻石奖牌 - } -} - -} // namespace flappybird diff --git a/examples/flappy_bird/GameOverLayer.h b/examples/flappy_bird/GameOverLayer.h deleted file mode 100644 index 50409f1..0000000 --- a/examples/flappy_bird/GameOverLayer.h +++ /dev/null @@ -1,62 +0,0 @@ -// ============================================================================ -// GameOverLayer.h - 游戏结束层 -// 描述: 显示游戏结束界面、得分和奖牌 -// ============================================================================ - -#pragma once - -#include - -namespace flappybird { - -/** - * @brief 游戏结束层类 - * 显示游戏结束后的得分面板和按钮 - */ -class GameOverLayer : public extra2d::Node { -public: - /** - * @brief 构造函数 - * @param score 本局得分 - */ - GameOverLayer(int score); - - /** - * @brief 进入场景时调用 - */ - void onEnter() override; - - /** - * @brief 每帧更新时调用 - * @param dt 时间间隔 - */ - void onUpdate(float dt) override; - -private: - /** - * @brief 初始化得分面板 - * @param score 本局得分 - * @param screenHeight 屏幕高度 - */ - void initPanel(int score, float screenHeight); - - /** - * @brief 初始化按钮 - */ - void initButtons(); - - /** - * @brief 根据得分获取奖牌 - * @param score 得分 - * @return 奖牌精灵帧 - */ - extra2d::Ptr getMedal(int score); - - int score_ = 0; // 本局得分 - bool animationDone_ = false; // 动画是否完成 - extra2d::Ptr restartBtn_; // 重新开始按钮 - extra2d::Ptr menuBtn_; // 菜单按钮 - extra2d::Ptr shareBtn_; // 分享按钮 -}; - -} // namespace flappybird diff --git a/examples/flappy_bird/GameScene.cpp b/examples/flappy_bird/GameScene.cpp deleted file mode 100644 index 5037220..0000000 --- a/examples/flappy_bird/GameScene.cpp +++ /dev/null @@ -1,226 +0,0 @@ -// ============================================================================ -// GameScene.cpp - 游戏主场景实现 -// ============================================================================ - -#include "GameScene.h" -#include "GameOverLayer.h" -#include "ResLoader.h" -#include "platform/input.h" - -namespace flappybird { - -GameScene::GameScene() { - // 基类 BaseScene 已经处理了视口设置和背景颜色 -} - -void GameScene::onEnter() { - BaseScene::onEnter(); - - // 游戏坐标系:使用游戏逻辑分辨率 - float screenWidth = GAME_WIDTH; - float screenHeight = GAME_HEIGHT; - - // 添加背景(使用左上角锚点,与原游戏一致) - auto bgFrame = ResLoader::getKeyFrame("bg_day"); - if (bgFrame) { - auto background = - extra2d::Sprite::create(bgFrame->getTexture(), bgFrame->getRect()); - background->setAnchor(extra2d::Vec2(0.0f, 0.0f)); - background->setPosition(extra2d::Vec2(0.0f, 0.0f)); - addChild(background); - } - - // 添加水管(初始时隐藏,游戏开始后才显示) - auto pipes = extra2d::makePtr(); - pipes_ = pipes.get(); - pipes->setVisible(false); - addChild(pipes); - - // 添加小鸟(在屏幕中间偏左位置) - auto bird = extra2d::makePtr(); - bird->setPosition( - extra2d::Vec2(screenWidth / 2.0f - 50.0f, screenHeight / 2.0f)); - bird_ = bird.get(); - addChild(bird); - - // 添加地面 - auto ground = extra2d::makePtr(); - ground_ = ground.get(); - addChild(ground); - - // 添加得分(屏幕顶部中央) - auto scoreNumber = extra2d::makePtr(); - scoreNumber->setPosition(extra2d::Vec2(screenWidth / 2.0f, 50.0f)); - scoreNumber->setNumber(0); - scoreNumber_ = scoreNumber.get(); - addChild(scoreNumber); - - // 添加 ready 图片(屏幕中央偏上) - auto readyFrame = ResLoader::getKeyFrame("text_ready"); - if (readyFrame) { - readySprite_ = extra2d::Sprite::create(readyFrame->getTexture(), - readyFrame->getRect()); - readySprite_->setAnchor(extra2d::Vec2(0.5f, 0.5f)); - readySprite_->setPosition( - extra2d::Vec2(screenWidth / 2.0f, screenHeight / 2.0f - 70.0f)); - addChild(readySprite_); - } - - // 添加教程图片(屏幕中央偏下) - auto tutorialFrame = ResLoader::getKeyFrame("tutorial"); - if (tutorialFrame) { - tutorialSprite_ = extra2d::Sprite::create(tutorialFrame->getTexture(), - tutorialFrame->getRect()); - tutorialSprite_->setAnchor(extra2d::Vec2(0.5f, 0.5f)); - tutorialSprite_->setPosition( - extra2d::Vec2(screenWidth / 2.0f, screenHeight / 2.0f + 30.0f)); - addChild(tutorialSprite_); - } - - // 播放转场音效 - ResLoader::playMusic(MusicType::Swoosh); - - // 初始化状态 - started_ = false; - score_ = 0; -} - -void GameScene::onUpdate(float dt) { - if (!gameOver_) { - if (!bird_) - return; - - auto &input = extra2d::Application::instance().input(); - - if (input.isButtonPressed(extra2d::GamepadButton::A) || - input.isMousePressed(extra2d::MouseButton::Left)) { - if (!started_) { - started_ = true; - startGame(); - } - bird_->jump(); - } - - if (started_) { - bird_->fall(dt); - - if (pipes_) { - Pipe *firstPipe = pipes_->getPipe(0); - if (firstPipe && !firstPipe->scored) { - float birdX = bird_->pos().x; - float pipeX = firstPipe->pos().x; - if (pipeX <= birdX) { - score_++; - scoreNumber_->setNumber(score_); - firstPipe->scored = true; - ResLoader::playMusic(MusicType::Point); - } - } - } - - if (bird_->isLiving() && checkCollision()) { - onHit(); - } - - if (bird_->isLiving() && GAME_HEIGHT - bird_->pos().y <= 123.0f) { - bird_->setPosition(extra2d::Vec2(bird_->pos().x, GAME_HEIGHT - 123.0f)); - bird_->setStatus(Bird::Status::Still); - onHit(); - } - } - } - - BaseScene::onUpdate(dt); -} - -void GameScene::startGame() { - // 隐藏 ready 和 tutorial 图片 - if (readySprite_) { - readySprite_->setVisible(false); - } - if (tutorialSprite_) { - tutorialSprite_->setVisible(false); - } - - // 显示并开始移动水管 - if (pipes_) { - pipes_->setVisible(true); - pipes_->start(); - } - - // 设置小鸟状态 - if (bird_) { - bird_->setStatus(Bird::Status::StartToFly); - } -} - -bool GameScene::checkCollision() { - if (!bird_ || !pipes_) - return false; - - extra2d::Rect birdBox = bird_->boundingBox(); - - // 检查与每个水管的碰撞 - for (int i = 0; i < 3; ++i) { - Pipe *pipe = pipes_->getPipe(i); - if (!pipe) - continue; - - // 检查与上水管的碰撞 - extra2d::Rect topBox = pipe->getTopPipeBox(); - if (birdBox.intersects(topBox)) { - return true; - } - - // 检查与下水管的碰撞 - extra2d::Rect bottomBox = pipe->getBottomPipeBox(); - if (birdBox.intersects(bottomBox)) { - return true; - } - } - - return false; -} - -void GameScene::onHit() { - if (!bird_->isLiving()) - return; - - // 小鸟死亡 - bird_->die(); - - // 停止地面滚动 - if (ground_) { - ground_->stop(); - } - - // 停止水管移动 - if (pipes_) { - pipes_->stop(); - } - - // 停止小鸟动画 - if (bird_) { - bird_->setStatus(Bird::Status::Still); - } - - // 隐藏得分 - if (scoreNumber_) { - scoreNumber_->setVisible(false); - } - - gameOver(); -} - -void GameScene::gameOver() { - if (gameOver_) - return; - - started_ = false; - gameOver_ = true; - - auto gameOverLayer = extra2d::makePtr(score_); - addChild(gameOverLayer); -} - -} // namespace flappybird diff --git a/examples/flappy_bird/GameScene.h b/examples/flappy_bird/GameScene.h deleted file mode 100644 index 0e6fa3b..0000000 --- a/examples/flappy_bird/GameScene.h +++ /dev/null @@ -1,72 +0,0 @@ -// ============================================================================ -// GameScene.h - 游戏主场景 -// 描述: 游戏的核心场景,包含小鸟、水管、地面和得分系统 -// ============================================================================ - -#pragma once - -#include "BaseScene.h" -#include "Bird.h" -#include "Pipes.h" -#include "Ground.h" -#include "Number.h" - -namespace flappybird { - -/** - * @brief 游戏主场景类 - * 游戏的核心场景,处理游戏逻辑、碰撞检测和得分 - */ -class GameScene : public BaseScene { -public: - /** - * @brief 构造函数 - */ - GameScene(); - - /** - * @brief 场景进入时调用 - */ - void onEnter() override; - - /** - * @brief 每帧更新时调用 - * @param dt 时间间隔(秒) - */ - void onUpdate(float dt) override; - -private: - /** - * @brief 开始游戏 - */ - void startGame(); - - /** - * @brief 处理碰撞事件 - */ - void onHit(); - - /** - * @brief 游戏结束 - */ - void gameOver(); - - /** - * @brief 检查小鸟与水管的碰撞 - * @return 是否发生碰撞 - */ - bool checkCollision(); - - Bird* bird_ = nullptr; // 小鸟 - Pipes* pipes_ = nullptr; // 水管管理器 - Ground* ground_ = nullptr; // 地面 - Number* scoreNumber_ = nullptr; // 得分显示 - extra2d::Ptr readySprite_; // "Get Ready" 提示 - extra2d::Ptr tutorialSprite_; // 操作教程提示 - - bool started_ = false; // 游戏是否已开始 - bool gameOver_ = false; // 游戏是否已结束 - int score_ = 0; // 当前得分 -}; - -} // namespace flappybird diff --git a/examples/flappy_bird/Number.cpp b/examples/flappy_bird/Number.cpp deleted file mode 100644 index 39f09e1..0000000 --- a/examples/flappy_bird/Number.cpp +++ /dev/null @@ -1,66 +0,0 @@ -// ============================================================================ -// Number.cpp - 数字显示类实现 -// ============================================================================ - -#include "Number.h" -#include "ResLoader.h" - -namespace flappybird { - -Number::Number() : number_(0) { -} - -void Number::setNumber(int number) { - number_ = number; - createNumberSprites(number, "number_big_"); -} - -void Number::setLittleNumber(int number) { - number_ = number; - createNumberSprites(number, "number_medium_"); -} - -void Number::createNumberSprites(int number, const std::string& prefix) { - // 清除之前的数字精灵 - removeAllChildren(); - - // 获取数字 0 的高度作为参考 - auto zeroFrame = ResLoader::getKeyFrame(prefix + "0"); - float digitHeight = zeroFrame ? zeroFrame->getRect().size.height : 36.0f; - - // 收集所有数字位 - std::vector digits; - if (number == 0) { - digits.push_back(0); - } else { - while (number > 0) { - digits.push_back(number % 10); - number /= 10; - } - } - - // 计算总宽度 - float totalWidth = 0.0f; - std::vector digitWidths; - for (int digit : digits) { - auto frame = ResLoader::getKeyFrame(prefix + std::to_string(digit)); - float width = frame ? frame->getRect().size.width : 24.0f; - digitWidths.push_back(width); - totalWidth += width; - } - - // 创建数字精灵并居中排列 - float currentX = -totalWidth / 2.0f; - for (size_t i = 0; i < digits.size(); ++i) { - auto frame = ResLoader::getKeyFrame(prefix + std::to_string(digits[i])); - if (frame) { - auto digitSprite = extra2d::Sprite::create(frame->getTexture(), frame->getRect()); - digitSprite->setAnchor(extra2d::Vec2(0.0f, 0.0f)); - digitSprite->setPosition(extra2d::Vec2(currentX, -digitHeight / 2.0f)); - addChild(digitSprite); - } - currentX += digitWidths[i]; - } -} - -} // namespace flappybird diff --git a/examples/flappy_bird/Number.h b/examples/flappy_bird/Number.h deleted file mode 100644 index 449a60c..0000000 --- a/examples/flappy_bird/Number.h +++ /dev/null @@ -1,52 +0,0 @@ -// ============================================================================ -// Number.h - 数字显示类 -// 描述: 将整数数字转换为精灵图片显示 -// ============================================================================ - -#pragma once - -#include - -namespace flappybird { - -/** - * @brief 数字显示类 - * 用于显示得分,将整数转换为对应的数字图片 - */ -class Number : public extra2d::Node { -public: - /** - * @brief 构造函数 - */ - Number(); - - /** - * @brief 设置显示的数字(大号) - * @param number 要显示的数字 - */ - void setNumber(int number); - - /** - * @brief 设置显示的数字(小号) - * @param number 要显示的数字 - */ - void setLittleNumber(int number); - - /** - * @brief 获取当前数字 - * @return 当前数字 - */ - int getNumber() const { return number_; } - -private: - /** - * @brief 创建数字精灵 - * @param number 数字值 - * @param prefix 数字图片前缀("number_big_" 或 "number_medium_") - */ - void createNumberSprites(int number, const std::string& prefix); - - int number_ = 0; // 当前数字 -}; - -} // namespace flappybird diff --git a/examples/flappy_bird/ResLoader.cpp b/examples/flappy_bird/ResLoader.cpp deleted file mode 100644 index 22d466c..0000000 --- a/examples/flappy_bird/ResLoader.cpp +++ /dev/null @@ -1,99 +0,0 @@ -// ============================================================================ -// ResLoader.cpp - 资源加载器实现 -// ============================================================================ - -#include "ResLoader.h" -#include - -namespace flappybird { - -extra2d::Ptr ResLoader::atlasTexture_; -std::map ResLoader::imageMap_; -std::map> ResLoader::soundMap_; - -void ResLoader::init() { - auto &resources = extra2d::Application::instance().resources(); - - // 加载图集纹理 - atlasTexture_ = resources.loadTexture("assets/images/atlas.png"); - if (!atlasTexture_) { - E2D_LOG_ERROR("无法加载图集纹理 atlas.png"); - return; - } - - // 使用资源管理器加载 JSON 文件 - std::string jsonContent = resources.loadJsonFile("assets/images/atlas.json"); - if (jsonContent.empty()) { - E2D_LOG_ERROR("无法加载 atlas.json 文件"); - return; - } - - // 解析 JSON 图集数据 - try { - nlohmann::json jsonData = nlohmann::json::parse(jsonContent); - - for (const auto &sprite : jsonData["sprites"]) { - std::string name = sprite["name"]; - float x = sprite["x"]; - float y = sprite["y"]; - float width = sprite["width"]; - float height = sprite["height"]; - - ImageInfo info = {width, height, x, y}; - imageMap_[name] = info; - } - - E2D_LOG_INFO("成功加载 {} 个精灵帧", imageMap_.size()); - } catch (const std::exception &e) { - E2D_LOG_ERROR("解析 atlas.json 失败: {}", e.what()); - return; - } - - // 加载音效 - soundMap_[MusicType::Click] = resources.loadSound("assets/sound/click.wav"); - soundMap_[MusicType::Hit] = resources.loadSound("assets/sound/hit.wav"); - soundMap_[MusicType::Fly] = resources.loadSound("assets/sound/fly.wav"); - soundMap_[MusicType::Point] = resources.loadSound("assets/sound/point.wav"); - soundMap_[MusicType::Swoosh] = resources.loadSound("assets/sound/swoosh.wav"); - - E2D_LOG_INFO("资源加载完成"); -} - -extra2d::Ptr -ResLoader::getKeyFrame(const std::string &name) { - auto it = imageMap_.find(name); - if (it == imageMap_.end()) { - E2D_LOG_WARN("找不到精灵帧: %s", name.c_str()); - return nullptr; - } - - const ImageInfo &info = it->second; - E2D_LOG_INFO("加载精灵帧: name={}, w={}, h={}, x={}, y={}", name, info.width, - info.height, info.x, info.y); - - // 检查纹理尺寸 - if (atlasTexture_) { - E2D_LOG_INFO("图集纹理尺寸: {}x{}", atlasTexture_->width(), - atlasTexture_->height()); - } - - return extra2d::makePtr( - atlasTexture_, extra2d::Rect(info.x, info.y, info.width, info.height)); -} - -void ResLoader::playMusic(MusicType type) { - auto it = soundMap_.find(type); - if (it == soundMap_.end()) { - E2D_LOG_WARN("ResLoader::playMusic: sound type not found"); - return; - } - if (!it->second) { - E2D_LOG_WARN("ResLoader::playMusic: sound pointer is null"); - return; - } - if (!it->second->play()) { - E2D_LOG_WARN("ResLoader::playMusic: failed to play sound"); - } -} - -} // namespace flappybird diff --git a/examples/flappy_bird/ResLoader.h b/examples/flappy_bird/ResLoader.h deleted file mode 100644 index 1babaeb..0000000 --- a/examples/flappy_bird/ResLoader.h +++ /dev/null @@ -1,63 +0,0 @@ -// ============================================================================ -// ResLoader.h - 资源加载器 -// 描述: 管理游戏资源的加载和访问 -// ============================================================================ - -#pragma once - -#include -#include -#include - -namespace flappybird { - -/** - * @brief 音频类型枚举 - */ -enum class MusicType { - Click, // 按键声音 - Hit, // 小鸟死亡声音 - Fly, // 小鸟飞翔声音 - Point, // 得分声音 - Swoosh // 转场声音 -}; - -/** - * @brief 资源加载器类 - * 管理纹理图集、精灵帧和音频资源的加载 - */ -class ResLoader { -public: - /** - * @brief 初始化资源加载器 - */ - static void init(); - - /** - * @brief 获取精灵帧 - * @param name 帧名称 - * @return 精灵帧指针 - */ - static extra2d::Ptr getKeyFrame(const std::string& name); - - /** - * @brief 播放音效 - * @param type 音效类型 - */ - static void playMusic(MusicType type); - -private: - /** - * @brief 图片信息结构 - * 对应 atlas.txt 格式: 元素名 width height x y - */ - struct ImageInfo { - float width, height, x, y; - }; - - static extra2d::Ptr atlasTexture_; // 图集纹理 - static std::map imageMap_; // 图片信息映射 - static std::map> soundMap_; // 音效映射 -}; - -} // namespace flappybird diff --git a/examples/flappy_bird/SplashScene.cpp b/examples/flappy_bird/SplashScene.cpp deleted file mode 100644 index 5f79d06..0000000 --- a/examples/flappy_bird/SplashScene.cpp +++ /dev/null @@ -1,49 +0,0 @@ -// ============================================================================ -// SplashScene.cpp - 启动场景实现 -// ============================================================================ - -#include "SplashScene.h" -#include "ResLoader.h" -#include "StartScene.h" -#include - -namespace flappybird { - -SplashScene::SplashScene() { - // 基类 BaseScene 已经处理了视口设置和背景颜色 -} - -void SplashScene::onEnter() { - BaseScene::onEnter(); - - // 尝试加载 splash 图片 - auto splashFrame = ResLoader::getKeyFrame("splash"); - if (splashFrame) { - auto splash = extra2d::Sprite::create(splashFrame->getTexture(), - splashFrame->getRect()); - splash->setAnchor(0.5f, 0.5f); - // splash 图片是全屏的(288x512),将其中心放在游戏区域中心 - splash->setPosition(GAME_WIDTH / 2.0f, GAME_HEIGHT / 2.0f); - addChild(splash); - } - // 播放转场音效 - ResLoader::playMusic(MusicType::Swoosh); -} - -void SplashScene::onUpdate(float dt) { - BaseScene::onUpdate(dt); - - // 计时 - timer_ += dt; - if (timer_ >= delay_) { - gotoStartScene(); - } -} - -void SplashScene::gotoStartScene() { - auto &app = extra2d::Application::instance(); - app.scenes().replaceScene(extra2d::makePtr(), - extra2d::TransitionType::Fade, 2.0f); -} - -} // namespace flappybird diff --git a/examples/flappy_bird/SplashScene.h b/examples/flappy_bird/SplashScene.h deleted file mode 100644 index 57e64e6..0000000 --- a/examples/flappy_bird/SplashScene.h +++ /dev/null @@ -1,44 +0,0 @@ -// ============================================================================ -// SplashScene.h - 启动场景 -// 描述: 显示游戏 Logo,2秒后自动跳转到开始场景 -// ============================================================================ - -#pragma once - -#include "BaseScene.h" - -namespace flappybird { - -/** - * @brief 启动场景类 - * 显示游戏 Logo,短暂延迟后进入主菜单 - */ -class SplashScene : public BaseScene { -public: - /** - * @brief 构造函数 - */ - SplashScene(); - - /** - * @brief 场景进入时调用 - */ - void onEnter() override; - - /** - * @brief 每帧更新时调用 - * @param dt 时间间隔(秒) - */ - void onUpdate(float dt) override; - -private: - /** - * @brief 跳转到开始场景 - */ - void gotoStartScene(); - - float timer_ = 0.0f; // 计时器 - const float delay_ = 2.0f; // 延迟时间(秒) -}; - -} // namespace flappybird diff --git a/examples/flappy_bird/StartScene.cpp b/examples/flappy_bird/StartScene.cpp deleted file mode 100644 index 84f8e43..0000000 --- a/examples/flappy_bird/StartScene.cpp +++ /dev/null @@ -1,145 +0,0 @@ -// ============================================================================ -// StartScene.cpp - 开始菜单场景实现 -// ============================================================================ - -#include "StartScene.h" -#include "Bird.h" -#include "GameScene.h" -#include "Ground.h" -#include "ResLoader.h" -#include "event/input_codes.h" - -namespace flappybird { - -StartScene::StartScene() { - // 基类 BaseScene 已经处理了视口设置和背景颜色 -} - -void StartScene::onEnter() { - BaseScene::onEnter(); - - // 使用游戏逻辑分辨率 - float screenWidth = GAME_WIDTH; - float screenHeight = GAME_HEIGHT; - - // 添加背景(使用左上角锚点) - auto bgFrame = ResLoader::getKeyFrame("bg_day"); - if (bgFrame) { - auto background = - extra2d::Sprite::create(bgFrame->getTexture(), bgFrame->getRect()); - background->setAnchor(0.0f, 0.0f); - background->setPosition(0.0f, 0.0f); - addChild(background); - E2D_LOG_INFO("背景已添加: size={} x {}", bgFrame->getRect().size.width, - bgFrame->getRect().size.height); - } else { - E2D_LOG_ERROR("无法加载背景图片"); - } - - // 添加地面 - auto ground = extra2d::makePtr(); - addChild(ground); - - // 添加标题图片(在上方) - auto titleFrame = ResLoader::getKeyFrame("title"); - if (titleFrame) { - auto title = extra2d::Sprite::create(titleFrame->getTexture(), - titleFrame->getRect()); - title->setAnchor(0.5f, 0.5f); - // 标题在屏幕上方 - title->setPosition(screenWidth / 2.0f, 150.0f); - addChild(title); - E2D_LOG_INFO("标题已添加: size={} x {}", titleFrame->getRect().size.width, - titleFrame->getRect().size.height); - } else { - E2D_LOG_ERROR("无法加载标题图片"); - } - - // 添加小鸟(在标题下方) - auto bird = extra2d::makePtr(); - bird->setAnchor(0.5f, 0.5f); - bird->setPosition(screenWidth / 2.0f, screenHeight / 2.0f); - bird->setStatus(Bird::Status::Idle); - addChild(bird); - - // 添加开始按钮 - 在小鸟下方 - auto playFrame = ResLoader::getKeyFrame("button_play"); - if (playFrame) { - float btnWidth = playFrame->getRect().size.width; - float btnHeight = playFrame->getRect().size.height; - - playBtn_ = extra2d::Button::create(); - playBtn_->setBackgroundImage(playFrame->getTexture(), playFrame->getRect()); - // 使用世界坐标,中心锚点 - playBtn_->setAnchor(0.5f, 0.5f); - // PLAY 按钮在小鸟下方 - playBtn_->setPosition(screenWidth / 2.0f, - screenHeight - playBtn_->size().height - 100.0f); - playBtn_->setOnClick([this]() { - ResLoader::playMusic(MusicType::Click); - startGame(); - }); - addChild(playBtn_); - } - - // 添加分享按钮 - 在 PLAY 按钮下方,靠近地面 - auto shareFrame = ResLoader::getKeyFrame("button_share"); - if (shareFrame) { - float btnWidth = shareFrame->getRect().size.width; - float btnHeight = shareFrame->getRect().size.height; - - shareBtn_ = extra2d::Button::create(); - shareBtn_->setBackgroundImage(shareFrame->getTexture(), - shareFrame->getRect()); - // 使用世界坐标,中心锚点 - shareBtn_->setAnchor(0.5f, 0.5f); - // SHARE 按钮在 PLAY 按钮下方,靠近地面 - shareBtn_->setPosition(screenWidth / 2.0f, - screenHeight - shareBtn_->size().height - 80.0f); - shareBtn_->setOnClick([this]() { - ResLoader::playMusic(MusicType::Click); - // 分享功能暂不实现 - }); - addChild(shareBtn_); - } - - // 添加 copyright 图片(在底部) - auto copyrightFrame = ResLoader::getKeyFrame("brand_copyright"); - if (copyrightFrame) { - auto copyright = extra2d::Sprite::create(copyrightFrame->getTexture(), - copyrightFrame->getRect()); - copyright->setAnchor(0.5f, 0.5f); - // Copyright 在屏幕底部 - copyright->setPosition(screenWidth / 2.0f, screenHeight - 20.0f); - addChild(copyright); - } - - // 播放转场音效 - ResLoader::playMusic(MusicType::Swoosh); -} - -void StartScene::onUpdate(float dt) { - BaseScene::onUpdate(dt); - - // 检测 A 键或空格开始游戏 - auto &input = extra2d::Application::instance().input(); - if (input.isButtonPressed(extra2d::GamepadButton::A)) { - ResLoader::playMusic(MusicType::Click); - startGame(); - } - - // 检测 BACK 键退出游戏 - if (input.isButtonPressed(extra2d::GamepadButton::Start)) { - ResLoader::playMusic(MusicType::Click); - auto &app = extra2d::Application::instance(); - app.quit(); - } -} - -void StartScene::startGame() { - auto &app = extra2d::Application::instance(); - app.scenes().replaceScene(extra2d::makePtr(), - extra2d::TransitionType::Fade, 0.5f); -} - -} // namespace flappybird diff --git a/examples/flappy_bird/StartScene.h b/examples/flappy_bird/StartScene.h deleted file mode 100644 index 01cc56d..0000000 --- a/examples/flappy_bird/StartScene.h +++ /dev/null @@ -1,53 +0,0 @@ -// ============================================================================ -// StartScene.h - 开始菜单场景 -// 描述: 显示游戏标题、开始按钮和版权信息 -// ============================================================================ - -#pragma once - -#include "BaseScene.h" - -namespace flappybird { - -/** - * @brief 开始场景类 - * 游戏主菜单,包含开始游戏按钮和版权信息 - */ -class StartScene : public BaseScene { -public: - /** - * @brief 构造函数 - */ - StartScene(); - - /** - * @brief 场景进入时调用 - */ - void onEnter() override; - - /** - * @brief 每帧更新时调用 - * @param dt 时间间隔(秒) - */ - void onUpdate(float dt) override; - -private: - /** - * @brief 创建菜单按钮 - */ - void createMenuButtons(); - - /** - * @brief 开始游戏 - */ - void startGame(); - - extra2d::Ptr playBtn_; // 开始按钮 - extra2d::Ptr shareBtn_; // 分享按钮 - extra2d::Ptr title_; // 标题精灵 - float titleFinalY_ = 0.0f; // 标题最终Y位置 - float titleAnimTime_ = 0.0f; // 标题动画时间 - static constexpr float TITLE_ANIM_DURATION = 0.5f; // 标题动画持续时间 -}; - -} // namespace flappybird diff --git a/examples/flappy_bird/bird.cpp b/examples/flappy_bird/bird.cpp deleted file mode 100644 index 785e67f..0000000 --- a/examples/flappy_bird/bird.cpp +++ /dev/null @@ -1,195 +0,0 @@ -// ============================================================================ -// Bird.cpp - 小鸟类实现 -// ============================================================================ - -#include "Bird.h" -#include "ResLoader.h" - -namespace flappybird { - -Bird::Bird() { setStatus(Status::Idle); } - -Bird::~Bird() = default; - -void Bird::onEnter() { - Node::onEnter(); - if (!sprite_) { - initAnimations(); - } -} - -void Bird::initAnimations() { - // 随机选择小鸟颜色(0-2) - int colorMode = extra2d::randomInt(0, 2); - std::string prefix = "bird" + std::to_string(colorMode) + "_"; - - // 加载动画帧序列: 0 -> 1 -> 2 -> 1 - int frameSequence[] = {0, 1, 2, 1}; - for (int frameIndex : frameSequence) { - auto frameSprite = - ResLoader::getKeyFrame(prefix + std::to_string(frameIndex)); - if (frameSprite) { - frames_.push_back(frameSprite); - } else { - E2D_LOG_WARN("无法加载动画帧: {}{}", prefix, frameIndex); - } - } - - // 创建精灵 - if (!frames_.empty()) { - sprite_ = extra2d::Sprite::create(); - setCurrentFrame(0); - addChild(sprite_); - E2D_LOG_INFO("小鸟动画创建成功: 颜色={}, 帧数={}", colorMode, - frames_.size()); - } else { - E2D_LOG_ERROR("小鸟动画创建失败: 没有找到任何动画帧"); - } -} - -void Bird::setCurrentFrame(int frameIndex) { - if (frames_.empty() || !sprite_) - return; - - frameIndex = frameIndex % static_cast(frames_.size()); - currentFrame_ = frameIndex; - - auto &frame = frames_[frameIndex]; - sprite_->setTexture(frame->getTexture()); - sprite_->setTextureRect(frame->getRect()); -} - -void Bird::updateFrameAnimation(float dt) { - if (frames_.empty() || status_ == Status::Still) - return; - - frameTimer_ += dt; - - float interval = frameInterval_; - if (status_ == Status::StartToFly) { - interval = 0.05f; // 2倍速度 - } - - while (frameTimer_ >= interval) { - frameTimer_ -= interval; - setCurrentFrame((currentFrame_ + 1) % static_cast(frames_.size())); - } -} - -void Bird::onUpdate(float dt) { - extra2d::Node::onUpdate(dt); - - // 更新帧动画 - updateFrameAnimation(dt); - - // 处理闲置动画(上下浮动) - if (status_ == Status::Idle) { - idleTimer_ += dt; - idleOffset_ = std::sin(idleTimer_ * 5.0f) * 4.0f; - } -} - -void Bird::onRender(extra2d::Renderer &renderer) { - // 精灵会自动渲染,这里只需要处理旋转和偏移 - if (sprite_) { - sprite_->setRotation(rotation_); - - // 应用闲置偏移 - if (status_ == Status::Idle) { - sprite_->setPosition(extra2d::Vec2(0.0f, idleOffset_)); - } else { - sprite_->setPosition(extra2d::Vec2(0.0f, 0.0f)); - } - } - - // 调用父类的 onRender 来渲染子节点 - Node::onRender(renderer); -} - -void Bird::fall(float dt) { - if (!living_) - return; - - // 更新垂直位置 - extra2d::Vec2 position = pos(); - position.y += speed_ * dt; - setPosition(position); - - // 应用重力 - speed_ += gravity * dt; - - // 限制顶部边界 - if (position.y < 0) { - position.y = 0; - setPosition(position); - speed_ = 0; - } - - // 根据速度计算旋转角度 - // 上升时抬头(-15度),下降时低头(最大90度) - if (speed_ < 0) { - rotation_ = -15.0f; - } else { - rotation_ = std::min(90.0f, speed_ * 0.15f); - } -} - -void Bird::jump() { - if (!living_) - return; - - // 给小鸟向上的速度 - speed_ = -jumpSpeed; - - // 设置状态为飞行 - setStatus(Status::Fly); - - // 播放音效 - ResLoader::playMusic(MusicType::Fly); -} - -void Bird::die() { - living_ = false; - - // 播放死亡音效 - ResLoader::playMusic(MusicType::Hit); -} - -void Bird::setStatus(Status status) { - status_ = status; - - switch (status) { - case Status::Still: - // 停止所有动画 - frameTimer_ = 0.0f; - break; - - case Status::Idle: - // 开始闲置动画 - frameInterval_ = 0.1f; // 正常速度 - idleTimer_ = 0.0f; - break; - - case Status::StartToFly: - // 停止闲置动画,加速翅膀扇动 - idleOffset_ = 0.0f; - break; - - case Status::Fly: - // 飞行状态 - break; - - default: - break; - } -} - -extra2d::Rect Bird::boundingBox() const { - extra2d::Vec2 position = pos(); - // 小鸟碰撞框大小约为 24x24 - float halfSize = 12.0f; - return extra2d::Rect(position.x - halfSize, position.y - halfSize, - halfSize * 2.0f, halfSize * 2.0f); -} - -} // namespace flappybird diff --git a/examples/flappy_bird/bird.h b/examples/flappy_bird/bird.h deleted file mode 100644 index 43f4377..0000000 --- a/examples/flappy_bird/bird.h +++ /dev/null @@ -1,130 +0,0 @@ -// ============================================================================ -// Bird.h - 小鸟类 -// 描述: 玩家控制的小鸟角色,包含飞行动画和物理效果 -// ============================================================================ - -#pragma once - -#include - -namespace flappybird { - -/** - * @brief 小鸟类 - * 游戏主角,包含飞行动画、重力模拟和状态管理 - */ -class Bird : public extra2d::Node { -public: - /** - * @brief 小鸟状态枚举 - */ - enum class Status { - Still, // 静止不动 - Idle, // 上下浮动(菜单展示) - StartToFly, // 开始飞行 - Fly // 飞行中 - }; - - /** - * @brief 构造函数 - */ - Bird(); - - /** - * @brief 析构函数 - */ - ~Bird(); - - /** - * @brief 每帧更新 - * @param dt 时间间隔(秒) - */ - void onUpdate(float dt) override; - - /** - * @brief 渲染小鸟 - * @param renderer 渲染后端 - */ - void onRender(extra2d::Renderer &renderer) override; - - /** - * @brief 进入场景时调用 - */ - void onEnter() override; - - /** - * @brief 模拟下落 - * @param dt 时间间隔(秒) - */ - void fall(float dt); - - /** - * @brief 跳跃 - */ - void jump(); - - /** - * @brief 死亡 - */ - void die(); - - /** - * @brief 设置小鸟状态 - * @param status 新状态 - */ - void setStatus(Status status); - - /** - * @brief 获取当前状态 - * @return 当前状态 - */ - Status getStatus() const { return status_; } - - /** - * @brief 检查是否存活 - * @return 是否存活 - */ - bool isLiving() const { return living_; } - - /** - * @brief 获取边界框(用于碰撞检测) - * @return 边界框 - */ - extra2d::Rect boundingBox() const override; - -private: - /** - * @brief 初始化动画 - */ - void initAnimations(); - - /** - * @brief 更新帧动画 - */ - void updateFrameAnimation(float dt); - - /** - * @brief 设置当前帧 - */ - void setCurrentFrame(int frameIndex); - - bool living_ = true; // 是否存活 - float speed_ = 0.0f; // 垂直速度 - float rotation_ = 0.0f; // 旋转角度 - Status status_ = Status::Idle; // 当前状态 - - // 动画相关 - extra2d::Ptr sprite_; // 精灵 - std::vector> frames_; // 动画帧 - int currentFrame_ = 0; // 当前帧索引 - float frameTimer_ = 0.0f; // 帧计时器 - float frameInterval_ = 0.1f; // 帧间隔(秒) - float idleTimer_ = 0.0f; // 闲置动画计时器 - float idleOffset_ = 0.0f; // 闲置偏移量 - - // 物理常量 - static constexpr float gravity = 1440.0f; // 重力加速度 - static constexpr float jumpSpeed = 432.0f; // 跳跃初速度 -}; - -} // namespace flappybird diff --git a/examples/flappy_bird/ground.cpp b/examples/flappy_bird/ground.cpp deleted file mode 100644 index 6c30c8d..0000000 --- a/examples/flappy_bird/ground.cpp +++ /dev/null @@ -1,79 +0,0 @@ -// ============================================================================ -// Ground.cpp - 地面类实现 -// ============================================================================ - -#include "Ground.h" -#include "BaseScene.h" -#include "ResLoader.h" - - -namespace flappybird { - -Ground::Ground() { - moving_ = true; - - // 使用游戏逻辑高度,而不是窗口高度 - float screenHeight = GAME_HEIGHT; - - // 获取地面纹理帧 - auto landFrame = ResLoader::getKeyFrame("land"); - if (!landFrame) - return; - - // 获取地面纹理和矩形 - auto texture = landFrame->getTexture(); - auto rect = landFrame->getRect(); - float groundWidth = rect.size.width; - float groundHeight = rect.size.height; - - // 创建第一块地面 - ground1_ = extra2d::Sprite::create(texture, rect); - ground1_->setAnchor(extra2d::Vec2(0.0f, 1.0f)); // 锚点设在左下角 - ground1_->setPosition(extra2d::Vec2(0.0f, screenHeight)); - addChild(ground1_); - - // 创建第二块地面,紧挨在第一块右边 - ground2_ = extra2d::Sprite::create(texture, rect); - ground2_->setAnchor(extra2d::Vec2(0.0f, 1.0f)); - ground2_->setPosition(extra2d::Vec2(groundWidth - 1.0f, screenHeight)); - addChild(ground2_); -} - -void Ground::onUpdate(float dt) { - extra2d::Node::onUpdate(dt); - - if (!moving_) - return; - if (!ground1_ || !ground2_) - return; - - // 获取地面宽度(从纹理矩形获取) - float groundWidth = ground1_->getTextureRect().size.width; - - // 移动两块地面 - extra2d::Vec2 pos1 = ground1_->pos(); - extra2d::Vec2 pos2 = ground2_->pos(); - - pos1.x -= speed * dt; - pos2.x -= speed * dt; - - // 当地面完全移出屏幕左侧时,重置到右侧 - if (pos1.x <= -groundWidth) { - pos1.x = pos2.x + groundWidth - 1.0f; - } - if (pos2.x <= -groundWidth) { - pos2.x = pos1.x + groundWidth - 1.0f; - } - - ground1_->setPosition(pos1); - ground2_->setPosition(pos2); -} - -void Ground::stop() { moving_ = false; } - -float Ground::height() const { - auto landFrame = ResLoader::getKeyFrame("land"); - return landFrame ? landFrame->getRect().size.height : 112.0f; -} - -} // namespace flappybird diff --git a/examples/flappy_bird/ground.h b/examples/flappy_bird/ground.h deleted file mode 100644 index 7cad090..0000000 --- a/examples/flappy_bird/ground.h +++ /dev/null @@ -1,48 +0,0 @@ -// ============================================================================ -// Ground.h - 地面类 -// 描述: 游戏底部不断向左滚动的地面 -// ============================================================================ - -#pragma once - -#include - -namespace flappybird { - -/** - * @brief 地面类 - * 游戏底部的滚动地面,由两块地面拼接而成 - */ -class Ground : public extra2d::Node { -public: - /** - * @brief 构造函数 - */ - Ground(); - - /** - * @brief 每帧更新 - * @param dt 时间间隔(秒) - */ - void onUpdate(float dt) override; - - /** - * @brief 停止地面滚动 - */ - void stop(); - - /** - * @brief 获取地面高度 - * @return 地面高度 - */ - float height() const; - -private: - extra2d::Ptr ground1_; // 第一块地面 - extra2d::Ptr ground2_; // 第二块地面 - - static constexpr float speed = 120.0f; // 滚动速度(像素/秒) - bool moving_ = true; // 是否正在滚动 -}; - -} // namespace flappybird diff --git a/examples/flappy_bird/main.cpp b/examples/flappy_bird/main.cpp deleted file mode 100644 index 6c2f6a2..0000000 --- a/examples/flappy_bird/main.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// ============================================================================ -// FlappyBird - Extra2D 示例程序 -// 作者: Extra2D Team -// 描述: 经典的 Flappy Bird 游戏实现 -// ============================================================================ - -#include "ResLoader.h" -#include "SplashScene.h" -#include - -using namespace extra2d; - -/** - * @brief 程序入口 - */ -int main(int argc, char **argv) { - // 初始化日志系统 - Logger::init(); - Logger::setLevel(LogLevel::Debug); - - E2D_LOG_INFO("========================"); - E2D_LOG_INFO("Extra2D FlappyBird"); - E2D_LOG_INFO("========================"); - - // 获取应用实例 - auto &app = Application::instance(); - - // 配置应用 - AppConfig config; - config.title = "Extra2D - FlappyBird"; - config.width = 1280; // 窗口宽度 (720P 分辨率) - config.height = 720; // 窗口高度 (720P 分辨率) - config.vsync = true; - config.fpsLimit = 60; - - // 初始化应用 - if (!app.init(config)) { - E2D_LOG_ERROR("应用初始化失败!"); - return -1; - } - - // 初始化资源加载器 - flappybird::ResLoader::init(); - - // 进入启动场景 - app.enterScene(makePtr()); - - E2D_LOG_INFO("开始主循环..."); - app.run(); - - E2D_LOG_INFO("应用结束"); - return 0; -} diff --git a/examples/flappy_bird/pipe.cpp b/examples/flappy_bird/pipe.cpp deleted file mode 100644 index 9f8b261..0000000 --- a/examples/flappy_bird/pipe.cpp +++ /dev/null @@ -1,107 +0,0 @@ -// ============================================================================ -// Pipe.cpp - 水管类实现 -// ============================================================================ - -#include "Pipe.h" -#include "BaseScene.h" -#include "ResLoader.h" - - -namespace flappybird { - -Pipe::Pipe() { - scored = false; - // 注意:不要在构造函数中创建子节点 - // 因为此时 weak_from_this() 还不能使用 -} - -void Pipe::onEnter() { - Node::onEnter(); - - // 在 onEnter 中创建子节点,此时 weak_from_this() 可用 - if (!topPipe_ && !bottomPipe_) { - // 使用游戏逻辑高度 - float screenHeight = GAME_HEIGHT; - - // 获取地面高度 - auto landFrame = ResLoader::getKeyFrame("land"); - float landHeight = landFrame ? landFrame->getRect().size.height : 112.0f; - - // 随机生成水管高度 - // 范围:与屏幕顶部最小距离不小于 100 像素 - // 与屏幕底部最小距离不小于地面上方 100 像素 - float minHeight = 100.0f; - float maxHeight = screenHeight - landHeight - 100.0f - gapHeight_; - float height = static_cast(extra2d::randomInt( - static_cast(minHeight), static_cast(maxHeight))); - - // 创建上水管 - auto topFrame = ResLoader::getKeyFrame("pipe_above"); - if (topFrame) { - topPipe_ = - extra2d::Sprite::create(topFrame->getTexture(), topFrame->getRect()); - topPipe_->setAnchor(extra2d::Vec2(0.5f, 1.0f)); // 锚点设在底部中心 - topPipe_->setPosition(extra2d::Vec2(0.0f, height - gapHeight_ / 2.0f)); - addChild(topPipe_); - } - - // 创建下水管 - auto bottomFrame = ResLoader::getKeyFrame("pipe_below"); - if (bottomFrame) { - bottomPipe_ = extra2d::Sprite::create(bottomFrame->getTexture(), - bottomFrame->getRect()); - bottomPipe_->setAnchor(extra2d::Vec2(0.5f, 0.0f)); // 锚点设在顶部中心 - bottomPipe_->setPosition(extra2d::Vec2(0.0f, height + gapHeight_ / 2.0f)); - addChild(bottomPipe_); - } - } -} - -Pipe::~Pipe() = default; - -extra2d::Rect Pipe::boundingBox() const { - // 返回整个水管的边界框(包含上下两根) - extra2d::Vec2 position = pos(); - - // 水管宽度约为 52 - float pipeWidth = 52.0f; - float halfWidth = pipeWidth / 2.0f; - - // 使用游戏逻辑高度 - float screenHeight = GAME_HEIGHT; - - return extra2d::Rect(position.x - halfWidth, 0.0f, pipeWidth, screenHeight); -} - -extra2d::Rect Pipe::getTopPipeBox() const { - if (!topPipe_) - return extra2d::Rect(); - - extra2d::Vec2 position = pos(); - extra2d::Vec2 topPos = topPipe_->pos(); - - // 上水管尺寸 - float pipeWidth = 52.0f; - float pipeHeight = 320.0f; - - return extra2d::Rect(position.x - pipeWidth / 2.0f, - position.y + topPos.y - pipeHeight, pipeWidth, - pipeHeight); -} - -extra2d::Rect Pipe::getBottomPipeBox() const { - if (!bottomPipe_) - return extra2d::Rect(); - - extra2d::Vec2 position = pos(); - extra2d::Vec2 bottomPos = bottomPipe_->pos(); - - // 下水管尺寸 - float pipeWidth = 52.0f; - float pipeHeight = 320.0f; - - return extra2d::Rect(position.x - pipeWidth / 2.0f, position.y + bottomPos.y, - pipeWidth, pipeHeight); -} - -} // namespace flappybird diff --git a/examples/flappy_bird/pipe.h b/examples/flappy_bird/pipe.h deleted file mode 100644 index 67c81c7..0000000 --- a/examples/flappy_bird/pipe.h +++ /dev/null @@ -1,59 +0,0 @@ -// ============================================================================ -// Pipe.h - 水管类 -// 描述: 游戏中的障碍物,由上下两根水管组成 -// ============================================================================ - -#pragma once - -#include - -namespace flappybird { - -/** - * @brief 水管类 - * 由上下两根水管组成的障碍物 - */ -class Pipe : public extra2d::Node { -public: - /** - * @brief 构造函数 - */ - Pipe(); - - /** - * @brief 析构函数 - */ - ~Pipe(); - - /** - * @brief 进入场景时调用 - */ - void onEnter() override; - - /** - * @brief 获取边界框(用于碰撞检测) - * @return 边界框 - */ - extra2d::Rect boundingBox() const override; - - /** - * @brief 获取上水管边界框 - * @return 边界框 - */ - extra2d::Rect getTopPipeBox() const; - - /** - * @brief 获取下水管边界框 - * @return 边界框 - */ - extra2d::Rect getBottomPipeBox() const; - - bool scored = false; // 是否已计分 - -private: - extra2d::Ptr topPipe_; // 上水管 - extra2d::Ptr bottomPipe_; // 下水管 - float gapHeight_ = 120.0f; // 间隙高度 -}; - -} // namespace flappybird diff --git a/examples/flappy_bird/pipes.cpp b/examples/flappy_bird/pipes.cpp deleted file mode 100644 index f8f6aa8..0000000 --- a/examples/flappy_bird/pipes.cpp +++ /dev/null @@ -1,106 +0,0 @@ -// ============================================================================ -// Pipes.cpp - 水管管理器实现 -// ============================================================================ - -#include "Pipes.h" -#include "BaseScene.h" - -namespace flappybird { - -Pipes::Pipes() { - pipeCount_ = 0; - moving_ = false; - - // 初始化水管数组 - for (int i = 0; i < maxPipes; ++i) { - pipes_[i] = nullptr; - } - - // 注意:不要在构造函数中添加水管 - // 因为此时 weak_from_this() 还不能使用 -} - -void Pipes::onEnter() { - Node::onEnter(); - // 在 onEnter 中初始化水管,此时 weak_from_this() 可用 - if (pipeCount_ == 0) { - addPipe(); - addPipe(); - addPipe(); - } -} - -Pipes::~Pipes() = default; - -void Pipes::onUpdate(float dt) { - extra2d::Node::onUpdate(dt); - - if (!moving_) return; - - // 移动所有水管 - for (int i = 0; i < pipeCount_; ++i) { - if (pipes_[i]) { - extra2d::Vec2 pos = pipes_[i]->pos(); - pos.x -= pipeSpeed * dt; - pipes_[i]->setPosition(pos); - } - } - - // 检查最前面的水管是否移出屏幕 - if (pipes_[0] && pipes_[0]->pos().x <= -30.0f) { - // 移除第一个水管(通过名称查找并移除) - // 由于 removeChild 需要 Ptr,我们使用 removeChildByName 或直接操作 - // 这里我们直接移除第一个子节点(假设它是水管) - auto childNodes = children(); - if (!childNodes.empty()) { - removeChild(childNodes[0]); - } - - // 将后面的水管前移 - for (int i = 0; i < pipeCount_ - 1; ++i) { - pipes_[i] = pipes_[i + 1]; - } - pipes_[pipeCount_ - 1] = nullptr; - pipeCount_--; - - // 添加新水管 - addPipe(); - } -} - -void Pipes::addPipe() { - if (pipeCount_ >= maxPipes) return; - - // 创建新水管 - auto pipe = extra2d::makePtr(); - - // 设置水管位置 - if (pipeCount_ == 0) { - // 第一个水管在屏幕外 130 像素处 - pipe->setPosition(extra2d::Vec2( - GAME_WIDTH + 130.0f, - 0.0f - )); - } else { - // 其他水管在前一个水管后方 - float prevX = pipes_[pipeCount_ - 1]->pos().x; - pipe->setPosition(extra2d::Vec2(prevX + pipeSpacing, 0.0f)); - } - - // 保存水管指针 - pipes_[pipeCount_] = pipe.get(); - pipeCount_++; - - // 添加到场景 - addChild(pipe); -} - -void Pipes::start() { - moving_ = true; -} - -void Pipes::stop() { - moving_ = false; -} - -} // namespace flappybird diff --git a/examples/flappy_bird/pipes.h b/examples/flappy_bird/pipes.h deleted file mode 100644 index b6f8a7f..0000000 --- a/examples/flappy_bird/pipes.h +++ /dev/null @@ -1,71 +0,0 @@ -// ============================================================================ -// Pipes.h - 水管管理器 -// 描述: 管理多个水管的生成、移动和回收 -// ============================================================================ - -#pragma once - -#include -#include "Pipe.h" - -namespace flappybird { - -/** - * @brief 水管管理器类 - * 管理游戏中的所有水管,负责生成、移动和回收 - */ -class Pipes : public extra2d::Node { -public: - /** - * @brief 构造函数 - */ - Pipes(); - - /** - * @brief 析构函数 - */ - ~Pipes(); - - /** - * @brief 每帧更新 - * @param dt 时间间隔(秒) - */ - void onUpdate(float dt) override; - - /** - * @brief 进入场景时调用 - */ - void onEnter() override; - - /** - * @brief 开始移动水管 - */ - void start(); - - /** - * @brief 停止移动水管 - */ - void stop(); - - /** - * @brief 获取当前水管数组 - * @return 水管指针数组 - */ - Pipe* getPipe(int index) { return (index >= 0 && index < 3) ? pipes_[index] : nullptr; } - -private: - /** - * @brief 添加一根新水管 - */ - void addPipe(); - - static constexpr int maxPipes = 3; // 最大水管数量 - static constexpr float pipeSpeed = 120.0f; // 水管移动速度(像素/秒) - static constexpr float pipeSpacing = 200.0f; // 水管间距 - - Pipe* pipes_[maxPipes]; // 水管数组 - int pipeCount_ = 0; // 当前水管数量 - bool moving_ = false; // 是否正在移动 -}; - -} // namespace flappybird diff --git a/examples/flappy_bird/romfs/assets/images/atlas.json b/examples/flappy_bird/romfs/assets/images/atlas.json deleted file mode 100644 index ce507c0..0000000 --- a/examples/flappy_bird/romfs/assets/images/atlas.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "sprites": [ - {"name": "splash", "width": 288, "height": 512, "x": 292, "y": 515}, - {"name": "bg_day", "width": 288, "height": 512, "x": 0, "y": 0}, - {"name": "bg_night", "width": 288, "height": 512, "x": 292, "y": 0}, - {"name": "bird0_0", "width": 34, "height": 24, "x": 5, "y": 982}, - {"name": "bird0_1", "width": 34, "height": 24, "x": 61, "y": 982}, - {"name": "bird0_2", "width": 34, "height": 24, "x": 117, "y": 982}, - {"name": "bird1_0", "width": 34, "height": 24, "x": 173, "y": 982}, - {"name": "bird1_1", "width": 34, "height": 24, "x": 229, "y": 658}, - {"name": "bird1_2", "width": 34, "height": 24, "x": 229, "y": 710}, - {"name": "bird2_0", "width": 34, "height": 24, "x": 229, "y": 762}, - {"name": "bird2_1", "width": 34, "height": 24, "x": 229, "y": 814}, - {"name": "bird2_2", "width": 34, "height": 24, "x": 229, "y": 866}, - {"name": "black", "width": 32, "height": 32, "x": 584, "y": 412}, - {"name": "blink_00", "width": 10, "height": 10, "x": 276, "y": 682}, - {"name": "blink_01", "width": 10, "height": 10, "x": 276, "y": 734}, - {"name": "blink_02", "width": 10, "height": 10, "x": 276, "y": 786}, - {"name": "brand_copyright", "width": 126, "height": 14, "x": 884, "y": 182}, - {"name": "button_ok", "width": 80, "height": 28, "x": 924, "y": 84}, - {"name": "button_pause", "width": 26, "height": 28, "x": 242, "y": 612}, - {"name": "button_rate", "width": 74, "height": 48, "x": 924, "y": 0}, - {"name": "button_resume", "width": 26, "height": 28, "x": 668, "y": 284}, - {"name": "button_score", "width": 116, "height": 70, "x": 822, "y": 234}, - {"name": "button_restart", "width": 116, "height": 70, "x": 702, "y": 316}, - {"name": "button_share_big", "width": 116, "height": 70, "x": 822, "y": 316}, - {"name": "button_play", "width": 116, "height": 70, "x": 702, "y": 234}, - {"name": "button_menu", "width": 80, "height": 28, "x": 924, "y": 52}, - {"name": "button_share", "width": 80, "height": 28, "x": 584, "y": 284}, - {"name": "land", "width": 336, "height": 112, "x": 584, "y": 0}, - {"name": "medals_0", "width": 44, "height": 44, "x": 224, "y": 954}, - {"name": "medals_1", "width": 44, "height": 44, "x": 224, "y": 906}, - {"name": "medals_2", "width": 44, "height": 44, "x": 242, "y": 564}, - {"name": "medals_3", "width": 44, "height": 44, "x": 242, "y": 516}, - {"name": "new", "width": 32, "height": 14, "x": 224, "y": 1002}, - {"name": "number_big_0", "width": 24, "height": 36, "x": 992, "y": 120}, - {"name": "number_big_1", "width": 16, "height": 36, "x": 272, "y": 910}, - {"name": "number_big_2", "width": 24, "height": 36, "x": 584, "y": 320}, - {"name": "number_big_3", "width": 24, "height": 36, "x": 612, "y": 320}, - {"name": "number_big_4", "width": 24, "height": 36, "x": 640, "y": 320}, - {"name": "number_big_5", "width": 24, "height": 36, "x": 668, "y": 320}, - {"name": "number_big_6", "width": 24, "height": 36, "x": 584, "y": 368}, - {"name": "number_big_7", "width": 24, "height": 36, "x": 612, "y": 368}, - {"name": "number_big_8", "width": 24, "height": 36, "x": 640, "y": 368}, - {"name": "number_big_9", "width": 24, "height": 36, "x": 668, "y": 368}, - {"name": "number_medium_0", "width": 16, "height": 20, "x": 272, "y": 612}, - {"name": "number_medium_1", "width": 16, "height": 20, "x": 272, "y": 954}, - {"name": "number_medium_2", "width": 16, "height": 20, "x": 272, "y": 978}, - {"name": "number_medium_3", "width": 16, "height": 20, "x": 260, "y": 1002}, - {"name": "number_medium_4", "width": 16, "height": 20, "x": 1002, "y": 0}, - {"name": "number_medium_5", "width": 16, "height": 20, "x": 1002, "y": 24}, - {"name": "number_medium_6", "width": 16, "height": 20, "x": 1008, "y": 52}, - {"name": "number_medium_7", "width": 16, "height": 20, "x": 1008, "y": 84}, - {"name": "number_medium_8", "width": 16, "height": 20, "x": 584, "y": 484}, - {"name": "number_medium_9", "width": 16, "height": 20, "x": 620, "y": 412}, - {"name": "number_small_0", "width": 12, "height": 14, "x": 276, "y": 646}, - {"name": "number_small_1", "width": 12, "height": 14, "x": 276, "y": 664}, - {"name": "number_small_2", "width": 12, "height": 14, "x": 276, "y": 698}, - {"name": "number_small_3", "width": 12, "height": 14, "x": 276, "y": 716}, - {"name": "number_small_4", "width": 12, "height": 14, "x": 276, "y": 750}, - {"name": "number_small_5", "width": 12, "height": 14, "x": 276, "y": 768}, - {"name": "number_small_6", "width": 12, "height": 14, "x": 276, "y": 802}, - {"name": "number_small_7", "width": 12, "height": 14, "x": 276, "y": 820}, - {"name": "number_small_8", "width": 12, "height": 14, "x": 276, "y": 854}, - {"name": "number_small_9", "width": 12, "height": 14, "x": 276, "y": 872}, - {"name": "number_context_10", "width": 12, "height": 14, "x": 992, "y": 164}, - {"name": "pipe_above_2", "width": 52, "height": 320, "x": 0, "y": 646}, - {"name": "pipe_below_2", "width": 52, "height": 320, "x": 56, "y": 646}, - {"name": "pipe_above", "width": 52, "height": 320, "x": 112, "y": 646}, - {"name": "pipe_below", "width": 52, "height": 320, "x": 168, "y": 646}, - {"name": "score_panel", "width": 238, "height": 126, "x": 0, "y": 516}, - {"name": "text_game_over", "width": 204, "height": 54, "x": 784, "y": 116}, - {"name": "text_ready", "width": 196, "height": 62, "x": 584, "y": 116}, - {"name": "title", "width": 178, "height": 48, "x": 702, "y": 182}, - {"name": "tutorial", "width": 114, "height": 98, "x": 584, "y": 182}, - {"name": "white", "width": 32, "height": 32, "x": 584, "y": 448} - ] -} \ No newline at end of file diff --git a/examples/flappy_bird/romfs/assets/images/atlas.png b/examples/flappy_bird/romfs/assets/images/atlas.png deleted file mode 100644 index 05e5ce1..0000000 Binary files a/examples/flappy_bird/romfs/assets/images/atlas.png and /dev/null differ diff --git a/examples/flappy_bird/romfs/assets/sound/click.wav b/examples/flappy_bird/romfs/assets/sound/click.wav deleted file mode 100644 index aef35cd..0000000 Binary files a/examples/flappy_bird/romfs/assets/sound/click.wav and /dev/null differ diff --git a/examples/flappy_bird/romfs/assets/sound/fly.wav b/examples/flappy_bird/romfs/assets/sound/fly.wav deleted file mode 100644 index 940adf2..0000000 Binary files a/examples/flappy_bird/romfs/assets/sound/fly.wav and /dev/null differ diff --git a/examples/flappy_bird/romfs/assets/sound/hit.wav b/examples/flappy_bird/romfs/assets/sound/hit.wav deleted file mode 100644 index 0fe1cf7..0000000 Binary files a/examples/flappy_bird/romfs/assets/sound/hit.wav and /dev/null differ diff --git a/examples/flappy_bird/romfs/assets/sound/point.wav b/examples/flappy_bird/romfs/assets/sound/point.wav deleted file mode 100644 index eb3961a..0000000 Binary files a/examples/flappy_bird/romfs/assets/sound/point.wav and /dev/null differ diff --git a/examples/flappy_bird/romfs/assets/sound/swoosh.wav b/examples/flappy_bird/romfs/assets/sound/swoosh.wav deleted file mode 100644 index e218eac..0000000 Binary files a/examples/flappy_bird/romfs/assets/sound/swoosh.wav and /dev/null differ diff --git a/examples/flappy_bird/xmake.lua b/examples/flappy_bird/xmake.lua deleted file mode 100644 index 85f1548..0000000 --- a/examples/flappy_bird/xmake.lua +++ /dev/null @@ -1,76 +0,0 @@ --- ============================================== --- FlappyBird 示例 - Xmake 构建脚本 --- 支持平台: MinGW (Windows), Nintendo Switch --- ============================================== - --- 获取当前脚本所在目录(示例根目录) -local example_dir = os.scriptdir() - --- 可执行文件目标 -target("flappy_bird") - set_kind("binary") - add_files("*.cpp") - add_includedirs("../../include") - add_deps("extra2d") - - -- 使用与主项目相同的平台配置 - if is_plat("switch") then - set_targetdir("../../build/examples/flappy_bird") - - -- 构建后生成 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, "flappy_bird.nacp") - local nro_file = path.join(output_dir, "flappy_bird.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", "FlappyBird", "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(), "flappy_bird.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/flappy_bird") - -- 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/push_box/BaseScene.cpp b/examples/push_box/BaseScene.cpp deleted file mode 100644 index 41834ad..0000000 --- a/examples/push_box/BaseScene.cpp +++ /dev/null @@ -1,138 +0,0 @@ -// ============================================================================ -// BaseScene.cpp - Push Box 基础场景实现 -// ============================================================================ - -#include "BaseScene.h" -#include -#include - -namespace pushbox { - -BaseScene::BaseScene() { - // 设置背景颜色为黑色(窗口四周会显示这个颜色) - setBackgroundColor(extra2d::Color(0.0f, 0.0f, 0.0f, 1.0f)); -} - -void BaseScene::onEnter() { - extra2d::Scene::onEnter(); - // 计算并更新视口 - updateViewport(); -} - -/** - * @brief 更新视口计算,使游戏内容在窗口中居中显示 - */ -void BaseScene::updateViewport() { - auto &app = extra2d::Application::instance(); - float windowWidth = static_cast(app.window().width()); - float windowHeight = static_cast(app.window().height()); - - // 计算游戏内容在窗口中的居中位置 - // 保持游戏原始宽高比,进行"黑边"适配 - float scaleX = windowWidth / GAME_WIDTH; - float scaleY = windowHeight / GAME_HEIGHT; - // 使用较小的缩放比例,确保游戏内容完整显示在窗口中 - float scale = std::min(scaleX, scaleY); - - scaledGameWidth_ = GAME_WIDTH * scale; - scaledGameHeight_ = GAME_HEIGHT * scale; - // 计算居中偏移,使游戏内容在窗口中水平和垂直居中 - viewportOffsetX_ = (windowWidth - scaledGameWidth_) * 0.5f; - viewportOffsetY_ = (windowHeight - scaledGameHeight_) * 0.5f; - - // 设置视口大小为游戏逻辑分辨率 - setViewportSize(GAME_WIDTH, GAME_HEIGHT); - - // 创建并设置相机 - auto camera = extra2d::makePtr(); - // 设置正交投影,覆盖整个游戏逻辑区域 - // 注意:对于2D游戏,Y轴向下增长,所以bottom > top - camera->setViewport(0.0f, GAME_WIDTH, GAME_HEIGHT, 0.0f); - setCamera(camera); -} - -/** - * @brief 渲染时调用,设置居中视口 - * @param renderer 渲染后端 - */ -void BaseScene::onRender(extra2d::Renderer &renderer) { - // 检查窗口大小是否改变,如果改变则更新视口 - auto &app = extra2d::Application::instance(); - float currentWindowWidth = static_cast(app.window().width()); - float currentWindowHeight = static_cast(app.window().height()); - - // 如果窗口大小改变,重新计算视口 - float expectedWidth = scaledGameWidth_ + viewportOffsetX_ * 2.0f; - float expectedHeight = scaledGameHeight_ + viewportOffsetY_ * 2.0f; - if (std::abs(currentWindowWidth - expectedWidth) > 1.0f || - std::abs(currentWindowHeight - expectedHeight) > 1.0f) { - E2D_LOG_INFO("BaseScene::onRender - window size changed from ({} x {}) to " - "({} x {}), updating viewport", - expectedWidth, expectedHeight, currentWindowWidth, - currentWindowHeight); - updateViewport(); - } - - // 设置视口为居中区域 - renderer.setViewport( - static_cast(viewportOffsetX_), static_cast(viewportOffsetY_), - static_cast(scaledGameWidth_), static_cast(scaledGameHeight_)); - - // 调用父类的 onRender 进行实际渲染 - extra2d::Scene::onRender(renderer); -} - -/** - * @brief 渲染场景内容,确保视口正确设置 - * @param renderer 渲染后端 - */ -void BaseScene::renderContent(extra2d::Renderer &renderer) { - // 如果视口参数未初始化(onEnter 还没被调用),先初始化 - if (scaledGameWidth_ <= 0.0f || scaledGameHeight_ <= 0.0f) { - updateViewport(); - } - - // 检查窗口大小是否改变 - auto &app = extra2d::Application::instance(); - float currentWindowWidth = static_cast(app.window().width()); - float currentWindowHeight = static_cast(app.window().height()); - - float expectedWidth = scaledGameWidth_ + viewportOffsetX_ * 2.0f; - float expectedHeight = scaledGameHeight_ + viewportOffsetY_ * 2.0f; - if (std::abs(currentWindowWidth - expectedWidth) > 1.0f || - std::abs(currentWindowHeight - expectedHeight) > 1.0f) { - updateViewport(); - } - - // 检查当前场景是否作为 TransitionScene 的子场景被渲染 - bool isChildOfTransition = false; - if (auto parentNode = parent()) { - if (dynamic_cast(parentNode.get())) { - isChildOfTransition = true; - } - } - - if (isChildOfTransition) { - // 作为 TransitionScene 的子场景时,需要设置正确的投影矩阵 - // 使用游戏逻辑分辨率作为投影区域,让 TransitionScene 控制整体视口 - auto camera = getActiveCamera(); - if (camera) { - // 设置投影矩阵覆盖整个游戏逻辑区域 - renderer.setViewProjection(camera->getViewProjectionMatrix()); - } - // 渲染场景内容(投影矩阵已设置,直接渲染) - batchUpdateTransforms(); - renderer.beginSpriteBatch(); - render(renderer); - renderer.endSpriteBatch(); - } else { - // 正常渲染时,调用父类的 renderContent 处理视口和投影 - renderer.setViewport(static_cast(viewportOffsetX_), - static_cast(viewportOffsetY_), - static_cast(scaledGameWidth_), - static_cast(scaledGameHeight_)); - extra2d::Scene::renderContent(renderer); - } -} - -} // namespace pushbox diff --git a/examples/push_box/BaseScene.h b/examples/push_box/BaseScene.h deleted file mode 100644 index f4b3892..0000000 --- a/examples/push_box/BaseScene.h +++ /dev/null @@ -1,57 +0,0 @@ -// ============================================================================ -// BaseScene.h - Push Box 基础场景类 -// 描述: 提供统一的居中视口适配功能,所有游戏场景都应继承此类 -// ============================================================================ - -#pragma once - -#include - -namespace pushbox { - -// 游戏逻辑分辨率 -static constexpr float GAME_WIDTH = 640.0f; -static constexpr float GAME_HEIGHT = 480.0f; - -/** - * @brief Push Box 基础场景类 - * 所有游戏场景都应继承此类,以获得统一的居中视口适配功能 - */ -class BaseScene : public extra2d::Scene { -public: - /** - * @brief 构造函数 - */ - BaseScene(); - - /** - * @brief 场景进入时调用 - */ - void onEnter() override; - - /** - * @brief 渲染时调用,设置居中视口 - * @param renderer 渲染后端 - */ - void onRender(extra2d::Renderer &renderer) override; - - /** - * @brief 渲染场景内容,确保视口正确设置 - * @param renderer 渲染后端 - */ - void renderContent(extra2d::Renderer &renderer) override; - -protected: - /** - * @brief 更新视口计算,使游戏内容在窗口中居中显示 - */ - void updateViewport(); - - // 视口适配参数(用于在窗口中居中显示游戏内容) - float scaledGameWidth_ = 0.0f; // 缩放后的游戏宽度 - float scaledGameHeight_ = 0.0f; // 缩放后的游戏高度 - float viewportOffsetX_ = 0.0f; // 视口水平偏移 - float viewportOffsetY_ = 0.0f; // 视口垂直偏移 -}; - -} // namespace pushbox diff --git a/examples/push_box/PlayScene.cpp b/examples/push_box/PlayScene.cpp deleted file mode 100644 index 6ec7e6d..0000000 --- a/examples/push_box/PlayScene.cpp +++ /dev/null @@ -1,488 +0,0 @@ -// ============================================================================ -// PlayScene.cpp - Push Box 游戏场景实现 -// ============================================================================ - -#include "PlayScene.h" - -#include "StartScene.h" -#include "SuccessScene.h" -#include "audio_manager.h" -#include "storage.h" -#include -#include - -namespace pushbox { - -/** - * @brief 加载字体 - * @param size 字体大小 - */ -static extra2d::Ptr loadFont(int size) { - auto &resources = extra2d::Application::instance().resources(); - auto font = resources.loadFont("assets/font.ttf", size); - return font; -} - -PlayScene::PlayScene(int level) : BaseScene() { - auto &app = extra2d::Application::instance(); - auto &resources = app.resources(); - - E2D_LOG_INFO("PlayScene: Loading textures..."); - - texWall_ = resources.loadTexture("assets/images/wall.gif"); - texPoint_ = resources.loadTexture("assets/images/point.gif"); - texFloor_ = resources.loadTexture("assets/images/floor.gif"); - texBox_ = resources.loadTexture("assets/images/box.gif"); - texBoxInPoint_ = resources.loadTexture("assets/images/boxinpoint.gif"); - - texMan_[1] = resources.loadTexture("assets/images/player/manup.gif"); - texMan_[2] = resources.loadTexture("assets/images/player/mandown.gif"); - texMan_[3] = resources.loadTexture("assets/images/player/manleft.gif"); - texMan_[4] = resources.loadTexture("assets/images/player/manright.gif"); - - texManPush_[1] = resources.loadTexture("assets/images/player/manhandup.gif"); - texManPush_[2] = - resources.loadTexture("assets/images/player/manhanddown.gif"); - texManPush_[3] = - resources.loadTexture("assets/images/player/manhandleft.gif"); - texManPush_[4] = - resources.loadTexture("assets/images/player/manhandright.gif"); - - font28_ = loadFont(28); - font20_ = loadFont(20); - - // 使用游戏逻辑分辨率 - float screenW = GAME_WIDTH; - float screenH = GAME_HEIGHT; - - // 计算游戏区域居中偏移 - float offsetX = (screenW - GAME_WIDTH) / 2.0f; - float offsetY = (screenH - GAME_HEIGHT) / 2.0f; - - // 音效开关按钮(使用 Button 的切换模式) - auto soundOn = resources.loadTexture("assets/images/soundon.png"); - auto soundOff = resources.loadTexture("assets/images/soundoff.png"); - if (soundOn && soundOff) { - soundBtn_ = extra2d::Button::create(); - soundBtn_->setToggleMode(true); - soundBtn_->setStateBackgroundImage(soundOff, soundOn); - soundBtn_->setOn(g_SoundOpen); - soundBtn_->setAnchor(0.0f, 0.0f); - soundBtn_->setPosition(offsetX + 50.0f, offsetY + 50.0f); - soundBtn_->setOnStateChange([](bool isOn) { - g_SoundOpen = isOn; - AudioManager::instance().setEnabled(isOn); - }); - addChild(soundBtn_); - } - - levelText_ = extra2d::Text::create("", font28_); - levelText_->setPosition(offsetX + 520.0f, offsetY + 30.0f); - levelText_->setTextColor(extra2d::Colors::White); - addChild(levelText_); - - stepText_ = extra2d::Text::create("", font20_); - stepText_->setPosition(offsetX + 520.0f, offsetY + 100.0f); - stepText_->setTextColor(extra2d::Colors::White); - addChild(stepText_); - - bestText_ = extra2d::Text::create("", font20_); - bestText_->setPosition(offsetX + 520.0f, offsetY + 140.0f); - bestText_->setTextColor(extra2d::Colors::White); - addChild(bestText_); - - // 创建菜单文本(使用颜色变化指示选中) - restartText_ = extra2d::Text::create("Y键重开", font20_); - restartText_->setPosition(offsetX + 520.0f, offsetY + 290.0f); - addChild(restartText_); - - soundToggleText_ = extra2d::Text::create("X键切换音效", font20_); - soundToggleText_->setPosition(offsetX + 520.0f, offsetY + 330.0f); - addChild(soundToggleText_); - - // 撤销提示(对象池使用示例) - undoText_ = extra2d::Text::create("Z键撤销", font20_); - undoText_->setPosition(offsetX + 520.0f, offsetY + 370.0f); - addChild(undoText_); - - mapLayer_ = extra2d::makePtr(); - mapLayer_->setAnchor(0.0f, 0.0f); - mapLayer_->setPosition(0.0f, 0.0f); - addChild(mapLayer_); - - setLevel(level); -} - -void PlayScene::onEnter() { - BaseScene::onEnter(); - if (soundBtn_) { - soundBtn_->setOn(g_SoundOpen); - } - updateMenuColors(); -} - -/** - * @brief 更新菜单颜色 - */ -void PlayScene::updateMenuColors() { - // 选中的项用红色,未选中的用白色 - if (restartText_) { - restartText_->setTextColor(menuIndex_ == 0 ? extra2d::Colors::Red - : extra2d::Colors::White); - } - if (soundToggleText_) { - soundToggleText_->setTextColor(menuIndex_ == 1 ? extra2d::Colors::Red - : extra2d::Colors::White); - } - if (undoText_) { - undoText_->setTextColor(menuIndex_ == 2 ? extra2d::Colors::Red - : extra2d::Colors::White); - } -} - -void PlayScene::onUpdate(float dt) { - BaseScene::onUpdate(dt); - - auto &app = extra2d::Application::instance(); - auto &input = app.input(); - - // B 键返回主菜单 - if (input.isButtonPressed(extra2d::GamepadButton::B)) { - app.scenes().replaceScene(extra2d::makePtr(), - extra2d::TransitionType::Fade, 0.5f); - return; - } - - // Y 键重开 - if (input.isButtonPressed(extra2d::GamepadButton::Y)) { - setLevel(g_CurrentLevel); - return; - } - - // X键直接切换音效(备用,按钮也可点击切换) - if (input.isButtonPressed(extra2d::GamepadButton::X)) { - g_SoundOpen = !g_SoundOpen; - AudioManager::instance().setEnabled(g_SoundOpen); - if (soundBtn_) { - soundBtn_->setOn(g_SoundOpen); - } - return; - } - - // Z 键撤销(对象池使用示例) - if (input.isKeyPressed(extra2d::Key::Z)) { - undoMove(); - return; - } - - // A 键执行选中的菜单项 - if (input.isButtonPressed(extra2d::GamepadButton::A)) { - executeMenuItem(); - return; - } - - // 方向键移动 - if (input.isButtonPressed(extra2d::GamepadButton::DPadUp)) { - move(0, -1, 1); - flush(); - } else if (input.isButtonPressed(extra2d::GamepadButton::DPadDown)) { - move(0, 1, 2); - flush(); - } else if (input.isButtonPressed(extra2d::GamepadButton::DPadLeft)) { - move(-1, 0, 3); - flush(); - } else if (input.isButtonPressed(extra2d::GamepadButton::DPadRight)) { - move(1, 0, 4); - flush(); - } else { - return; - } - - // 检查是否通关 - for (int i = 0; i < map_.width; i++) { - for (int j = 0; j < map_.height; j++) { - Piece p = map_.value[j][i]; - if (p.type == TYPE::Box && p.isPoint == false) { - return; - } - } - } - - gameOver(); -} - -/** - * @brief 执行选中的菜单项 - */ -void PlayScene::executeMenuItem() { - switch (menuIndex_) { - case 0: // 重开 - setLevel(g_CurrentLevel); - break; - case 1: // 切换音效 - g_SoundOpen = !g_SoundOpen; - AudioManager::instance().setEnabled(g_SoundOpen); - if (soundBtn_) { - soundBtn_->setOn(g_SoundOpen); - } - break; - case 2: // 撤销 - undoMove(); - break; - } -} - -/** - * @brief 刷新地图显示 - */ -void PlayScene::flush() { - mapLayer_->removeAllChildren(); - - int tileW = texFloor_ ? texFloor_->width() : 32; - int tileH = texFloor_ ? texFloor_->height() : 32; - - // 使用游戏逻辑分辨率 - float gameWidth = GAME_WIDTH; - float gameHeight = GAME_HEIGHT; - float baseOffsetX = 0.0f; - float baseOffsetY = 0.0f; - - // 在 12x12 网格中居中地图 - float mapOffsetX = static_cast((12.0f - map_.width) / 2.0f) * tileW; - float mapOffsetY = static_cast((12.0f - map_.height) / 2.0f) * tileH; - - float offsetX = baseOffsetX + mapOffsetX; - float offsetY = baseOffsetY + mapOffsetY; - - for (int i = 0; i < map_.width; i++) { - for (int j = 0; j < map_.height; j++) { - Piece piece = map_.value[j][i]; - - extra2d::Ptr tex; - - if (piece.type == TYPE::Wall) { - tex = texWall_; - } else if (piece.type == TYPE::Ground && piece.isPoint) { - tex = texPoint_; - } else if (piece.type == TYPE::Ground) { - tex = texFloor_; - } else if (piece.type == TYPE::Box && piece.isPoint) { - tex = texBoxInPoint_; - } else if (piece.type == TYPE::Box) { - tex = texBox_; - } else if (piece.type == TYPE::Man && g_Pushing) { - tex = texManPush_[g_Direct]; - } else if (piece.type == TYPE::Man) { - tex = texMan_[g_Direct]; - } else { - continue; - } - - if (!tex) { - continue; - } - - auto sprite = extra2d::Sprite::create(tex); - sprite->setAnchor(0.0f, 0.0f); - sprite->setPosition(offsetX + static_cast(i * tileW), - offsetY + static_cast(j * tileH)); - mapLayer_->addChild(sprite); - } - } -} - -/** - * @brief 设置关卡 - * @param level 关卡编号 - */ -void PlayScene::setLevel(int level) { - g_CurrentLevel = level; - saveCurrentLevel(g_CurrentLevel); - - // 清空移动历史(智能指针自动回收到对象池) - while (!moveHistory_.empty()) { - moveHistory_.pop(); - } - - if (levelText_) { - levelText_->setText("第" + std::to_string(level) + "关"); - } - - setStep(0); - - int bestStep = loadBestStep(level, 0); - if (bestText_) { - if (bestStep != 0) { - bestText_->setText("最佳" + std::to_string(bestStep) + "步"); - } else { - bestText_->setText(""); - } - } - - // 深拷贝地图数据 - Map &sourceMap = g_Maps[level - 1]; - map_.width = sourceMap.width; - map_.height = sourceMap.height; - map_.roleX = sourceMap.roleX; - map_.roleY = sourceMap.roleY; - for (int i = 0; i < 12; i++) { - for (int j = 0; j < 12; j++) { - map_.value[i][j] = sourceMap.value[i][j]; - } - } - - g_Direct = 2; - g_Pushing = false; - flush(); -} - -/** - * @brief 设置步数 - * @param step 步数 - */ -void PlayScene::setStep(int step) { - step_ = step; - if (stepText_) { - stepText_->setText("当前" + std::to_string(step) + "步"); - } -} - -/** - * @brief 移动玩家 - * @param dx X方向偏移 - * @param dy Y方向偏移 - * @param direct 方向(1=上,2=下,3=左,4=右) - */ -void PlayScene::move(int dx, int dy, int direct) { - int targetX = dx + map_.roleX; - int targetY = dy + map_.roleY; - g_Direct = direct; - - if (targetX < 0 || targetX >= map_.width || targetY < 0 || - targetY >= map_.height) { - return; - } - - if (map_.value[targetY][targetX].type == TYPE::Wall) { - return; - } - - // 使用对象池创建移动记录(自动管理内存) - auto record = E2D_MAKE_POOLED(MoveRecord, map_.roleX, map_.roleY, targetX, - targetY, false); - - if (map_.value[targetY][targetX].type == TYPE::Ground) { - g_Pushing = false; - map_.value[map_.roleY][map_.roleX].type = TYPE::Ground; - map_.value[targetY][targetX].type = TYPE::Man; - AudioManager::instance().playManMove(); - } else if (map_.value[targetY][targetX].type == TYPE::Box) { - g_Pushing = true; - - int boxX = 0; - int boxY = 0; - switch (g_Direct) { - case 1: - boxX = targetX; - boxY = targetY - 1; - break; - case 2: - boxX = targetX; - boxY = targetY + 1; - break; - case 3: - boxX = targetX - 1; - boxY = targetY; - break; - case 4: - boxX = targetX + 1; - boxY = targetY; - break; - default: - return; - } - - if (boxX < 0 || boxX >= map_.width || boxY < 0 || boxY >= map_.height) { - return; - } - - if (map_.value[boxY][boxX].type == TYPE::Wall || - map_.value[boxY][boxX].type == TYPE::Box) { - return; - } - - // 记录箱子移动 - record->pushedBox = true; - record->boxFromX = targetX; - record->boxFromY = targetY; - record->boxToX = boxX; - record->boxToY = boxY; - - map_.value[boxY][boxX].type = TYPE::Box; - map_.value[targetY][targetX].type = TYPE::Man; - map_.value[map_.roleY][map_.roleX].type = TYPE::Ground; - - AudioManager::instance().playBoxMove(); - } else { - return; - } - - // 保存移动记录到历史栈 - moveHistory_.push(record); - - map_.roleX = targetX; - map_.roleY = targetY; - setStep(step_ + 1); -} - -/** - * @brief 游戏通关 - */ -void PlayScene::gameOver() { - int bestStep = loadBestStep(g_CurrentLevel, 0); - if (bestStep == 0 || step_ < bestStep) { - saveBestStep(g_CurrentLevel, step_); - } - - if (g_CurrentLevel == MAX_LEVEL) { - extra2d::Application::instance().scenes().pushScene( - extra2d::makePtr(), extra2d::TransitionType::Fade, 0.5f); - return; - } - - setLevel(g_CurrentLevel + 1); -} - -/** - * @brief 撤销上一步移动(对象池使用示例) - * 智能指针离开作用域时自动回收到对象池 - */ -void PlayScene::undoMove() { - if (moveHistory_.empty()) { - E2D_LOG_INFO("No moves to undo"); - return; - } - - auto record = moveHistory_.top(); - moveHistory_.pop(); - - // 恢复玩家位置 - map_.value[map_.roleY][map_.roleX].type = TYPE::Ground; - map_.value[record->fromY][record->fromX].type = TYPE::Man; - map_.roleX = record->fromX; - map_.roleY = record->fromY; - - // 如果推了箱子,恢复箱子位置 - if (record->pushedBox) { - map_.value[record->boxToY][record->boxToX].type = TYPE::Ground; - map_.value[record->boxFromY][record->boxFromX].type = TYPE::Box; - } - - // record 智能指针离开作用域后自动回收到对象池 - setStep(step_ - 1); - flush(); - - E2D_LOG_INFO("Undo move, step: {}", step_); -} - -} // namespace pushbox diff --git a/examples/push_box/PlayScene.h b/examples/push_box/PlayScene.h deleted file mode 100644 index 009994a..0000000 --- a/examples/push_box/PlayScene.h +++ /dev/null @@ -1,113 +0,0 @@ -// ============================================================================ -// PlayScene.h - Push Box 游戏场景 -// ============================================================================ - -#pragma once - -#include "BaseScene.h" -#include "data.h" -#include -#include -#include - -namespace pushbox { - -/** - * @brief Push Box 游戏场景 - */ -class PlayScene : public BaseScene { -public: - /** - * @brief 构造函数 - * @param level 关卡编号 - */ - explicit PlayScene(int level); - - /** - * @brief 场景进入时调用 - */ - void onEnter() override; - - /** - * @brief 每帧更新 - * @param dt 帧间隔时间 - */ - void onUpdate(float dt) override; - -private: - /** - * @brief 更新菜单颜色 - */ - void updateMenuColors(); - - /** - * @brief 执行选中的菜单项 - */ - void executeMenuItem(); - - /** - * @brief 刷新地图显示 - */ - void flush(); - - /** - * @brief 设置关卡 - * @param level 关卡编号 - */ - void setLevel(int level); - - /** - * @brief 设置步数 - * @param step 步数 - */ - void setStep(int step); - - /** - * @brief 移动玩家 - * @param dx X方向偏移 - * @param dy Y方向偏移 - * @param direct 方向(1=上,2=下,3=左,4=右) - */ - void move(int dx, int dy, int direct); - - /** - * @brief 游戏通关 - */ - void gameOver(); - - /** - * @brief 撤销上一步移动(对象池使用示例) - */ - void undoMove(); - - int step_ = 0; - int menuIndex_ = 0; - Map map_{}; - - extra2d::Ptr font28_; - extra2d::Ptr font20_; - - extra2d::Ptr levelText_; - extra2d::Ptr stepText_; - extra2d::Ptr bestText_; - extra2d::Ptr restartText_; - extra2d::Ptr soundToggleText_; - extra2d::Ptr undoText_; - extra2d::Ptr mapLayer_; - - extra2d::Ptr soundBtn_; - - extra2d::Ptr texWall_; - extra2d::Ptr texPoint_; - extra2d::Ptr texFloor_; - extra2d::Ptr texBox_; - extra2d::Ptr texBoxInPoint_; - - extra2d::Ptr texMan_[5]; - extra2d::Ptr texManPush_[5]; - - // 对象池使用示例:使用智能指针管理 MoveRecord - std::stack> moveHistory_; -}; - -} // namespace pushbox diff --git a/examples/push_box/StartScene.cpp b/examples/push_box/StartScene.cpp deleted file mode 100644 index bb3f054..0000000 --- a/examples/push_box/StartScene.cpp +++ /dev/null @@ -1,227 +0,0 @@ -// ============================================================================ -// StartScene.cpp - Push Box 开始场景实现 -// ============================================================================ - -#include "StartScene.h" - -#include "PlayScene.h" -#include "audio_manager.h" -#include "data.h" -#include - -namespace pushbox { - -StartScene::StartScene() : BaseScene() { - // BaseScene 已处理视口设置 -} - -/** - * @brief 加载菜单字体 - */ -static extra2d::Ptr loadMenuFont() { - auto &resources = extra2d::Application::instance().resources(); - auto font = resources.loadFont("assets/font.ttf", 28, true); - return font; -} - -void StartScene::onEnter() { - BaseScene::onEnter(); - - auto &app = extra2d::Application::instance(); - auto &resources = app.resources(); - - if (children().empty()) { - // 使用游戏逻辑分辨率 - float screenW = GAME_WIDTH; - float screenH = GAME_HEIGHT; - - auto bgTex = resources.loadTexture("assets/images/start.jpg"); - if (bgTex) { - auto background = extra2d::Sprite::create(bgTex); - float bgWidth = static_cast(bgTex->width()); - float bgHeight = static_cast(bgTex->height()); - float offsetX = (screenW - bgWidth) / 2.0f; - float offsetY = (screenH - bgHeight) / 2.0f; - - background->setAnchor(0.0f, 0.0f); - background->setPosition(offsetX, offsetY); - addChild(background); - - float centerX = screenW / 2.0f; - - font_ = loadMenuFont(); - if (!font_) { - return; - } - - // 创建菜单按钮(使用 Button 实现文本居中) - startBtn_ = extra2d::Button::create(); - startBtn_->setFont(font_); - startBtn_->setText("新游戏"); - startBtn_->setTextColor(extra2d::Colors::Black); - startBtn_->setBackgroundColor(extra2d::Colors::Transparent, - extra2d::Colors::Transparent, - extra2d::Colors::Transparent); - startBtn_->setBorder(extra2d::Colors::Transparent, 0.0f); - startBtn_->setPadding(extra2d::Vec2(0.0f, 0.0f)); - startBtn_->setCustomSize(200.0f, 40.0f); - startBtn_->setAnchor(0.5f, 0.5f); - startBtn_->setPosition(centerX, offsetY + 260.0f); - addChild(startBtn_); - - resumeBtn_ = extra2d::Button::create(); - resumeBtn_->setFont(font_); - resumeBtn_->setText("继续关卡"); - resumeBtn_->setTextColor(extra2d::Colors::Black); - resumeBtn_->setBackgroundColor(extra2d::Colors::Transparent, - extra2d::Colors::Transparent, - extra2d::Colors::Transparent); - resumeBtn_->setBorder(extra2d::Colors::Transparent, 0.0f); - resumeBtn_->setPadding(extra2d::Vec2(0.0f, 0.0f)); - resumeBtn_->setCustomSize(200.0f, 40.0f); - resumeBtn_->setAnchor(0.5f, 0.5f); - resumeBtn_->setPosition(centerX, offsetY + 300.0f); - addChild(resumeBtn_); - - exitBtn_ = extra2d::Button::create(); - exitBtn_->setFont(font_); - exitBtn_->setText("退出"); - exitBtn_->setTextColor(extra2d::Colors::Black); - exitBtn_->setBackgroundColor(extra2d::Colors::Transparent, - extra2d::Colors::Transparent, - extra2d::Colors::Transparent); - exitBtn_->setBorder(extra2d::Colors::Transparent, 0.0f); - exitBtn_->setPadding(extra2d::Vec2(0.0f, 0.0f)); - exitBtn_->setCustomSize(200.0f, 40.0f); - exitBtn_->setAnchor(0.5f, 0.5f); - exitBtn_->setPosition(centerX, offsetY + 340.0f); - addChild(exitBtn_); - - // 音效开关按钮(使用 Button 的切换模式) - auto soundOn = resources.loadTexture("assets/images/soundon.png"); - auto soundOff = resources.loadTexture("assets/images/soundoff.png"); - if (soundOn && soundOff) { - soundBtn_ = extra2d::Button::create(); - soundBtn_->setToggleMode(true); - soundBtn_->setStateBackgroundImage(soundOff, soundOn); - soundBtn_->setOn(g_SoundOpen); - soundBtn_->setAnchor(0.0f, 0.0f); - soundBtn_->setPosition(offsetX + 50.0f, offsetY + 50.0f); - soundBtn_->setOnStateChange([](bool isOn) { - g_SoundOpen = isOn; - AudioManager::instance().setEnabled(isOn); - }); - addChild(soundBtn_); - } - } - } - - // 始终有3个菜单项 - menuCount_ = 3; - updateMenuColors(); -} - -void StartScene::onUpdate(float dt) { - BaseScene::onUpdate(dt); - - auto &app = extra2d::Application::instance(); - auto &input = app.input(); - - // 方向键上下切换选择 - if (input.isButtonPressed(extra2d::GamepadButton::DPadUp)) { - selectedIndex_ = (selectedIndex_ - 1 + menuCount_) % menuCount_; - updateMenuColors(); - } else if (input.isButtonPressed(extra2d::GamepadButton::DPadDown)) { - selectedIndex_ = (selectedIndex_ + 1) % menuCount_; - updateMenuColors(); - } - - // A键确认 - if (input.isButtonPressed(extra2d::GamepadButton::A)) { - executeMenuItem(); - } - - // X键切换音效(备用,按钮也可点击切换) - if (input.isButtonPressed(extra2d::GamepadButton::X)) { - g_SoundOpen = !g_SoundOpen; - AudioManager::instance().setEnabled(g_SoundOpen); - if (soundBtn_) { - soundBtn_->setOn(g_SoundOpen); - } - } -} - -/** - * @brief 更新菜单颜色 - */ -void StartScene::updateMenuColors() { - // 根据选中状态更新按钮文本颜色 - // 选中的项用红色,未选中的用黑色,禁用的项用深灰色 - - if (startBtn_) { - startBtn_->setTextColor(selectedIndex_ == 0 ? extra2d::Colors::Red - : extra2d::Colors::Black); - } - - if (resumeBtn_) { - // "继续关卡"始终显示,但当 g_CurrentLevel == 1 时禁用(深灰色) - if (g_CurrentLevel > 1) { - // 可用状态:选中为红色,未选中为黑色 - resumeBtn_->setTextColor(selectedIndex_ == 1 ? extra2d::Colors::Red - : extra2d::Colors::Black); - } else { - // 禁用状态:深灰色 (RGB: 80, 80, 80) - resumeBtn_->setTextColor(extra2d::Color(80, 80, 80, 255)); - } - } - - if (exitBtn_) { - exitBtn_->setTextColor(selectedIndex_ == 2 ? extra2d::Colors::Red - : extra2d::Colors::Black); - } -} - -/** - * @brief 执行选中的菜单项 - */ -void StartScene::executeMenuItem() { - // 始终有3个选项,但"继续关卡"(索引1)在 g_CurrentLevel == 1 时禁用 - switch (selectedIndex_) { - case 0: - startNewGame(); - break; - case 1: - // 只有当 g_CurrentLevel > 1 时才能选择"继续关卡" - if (g_CurrentLevel > 1) { - continueGame(); - } - break; - case 2: - exitGame(); - break; - } -} - -/** - * @brief 开始新游戏 - */ -void StartScene::startNewGame() { - extra2d::Application::instance().scenes().replaceScene( - extra2d::makePtr(1), extra2d::TransitionType::Fade, 0.5f); -} - -/** - * @brief 继续游戏 - */ -void StartScene::continueGame() { - extra2d::Application::instance().scenes().replaceScene( - extra2d::makePtr(g_CurrentLevel), - extra2d::TransitionType::Fade, 0.5f); -} - -/** - * @brief 退出游戏 - */ -void StartScene::exitGame() { extra2d::Application::instance().quit(); } - -} // namespace pushbox diff --git a/examples/push_box/StartScene.h b/examples/push_box/StartScene.h deleted file mode 100644 index 0f338b6..0000000 --- a/examples/push_box/StartScene.h +++ /dev/null @@ -1,68 +0,0 @@ -// ============================================================================ -// StartScene.h - Push Box 开始场景 -// ============================================================================ - -#pragma once - -#include "BaseScene.h" -#include - -namespace pushbox { - -/** - * @brief Push Box 开始场景(主菜单) - */ -class StartScene : public BaseScene { -public: - /** - * @brief 构造函数 - */ - StartScene(); - - /** - * @brief 场景进入时调用 - */ - void onEnter() override; - - /** - * @brief 每帧更新 - * @param dt 帧间隔时间 - */ - void onUpdate(float dt) override; - -private: - /** - * @brief 更新菜单颜色 - */ - void updateMenuColors(); - - /** - * @brief 执行选中的菜单项 - */ - void executeMenuItem(); - - /** - * @brief 开始新游戏 - */ - void startNewGame(); - - /** - * @brief 继续游戏 - */ - void continueGame(); - - /** - * @brief 退出游戏 - */ - void exitGame(); - - extra2d::Ptr font_; - extra2d::Ptr startBtn_; - extra2d::Ptr resumeBtn_; - extra2d::Ptr exitBtn_; - extra2d::Ptr soundBtn_; - int selectedIndex_ = 0; - int menuCount_ = 3; -}; - -} // namespace pushbox diff --git a/examples/push_box/SuccessScene.cpp b/examples/push_box/SuccessScene.cpp deleted file mode 100644 index c8b9401..0000000 --- a/examples/push_box/SuccessScene.cpp +++ /dev/null @@ -1,81 +0,0 @@ -// ============================================================================ -// SuccessScene.cpp - Push Box 通关场景实现 -// ============================================================================ - -#include "SuccessScene.h" - -#include - -namespace pushbox { - -SuccessScene::SuccessScene() : BaseScene() { - // BaseScene 已处理视口设置 -} - -/** - * @brief 加载菜单字体 - */ -static extra2d::Ptr loadMenuFont() { - auto &resources = extra2d::Application::instance().resources(); - auto font = resources.loadFont("assets/font.ttf", 28); - return font; -} - -void SuccessScene::onEnter() { - BaseScene::onEnter(); - - auto &app = extra2d::Application::instance(); - auto &resources = app.resources(); - - if (children().empty()) { - // 使用游戏逻辑分辨率 - float screenW = GAME_WIDTH; - float screenH = GAME_HEIGHT; - - auto bgTex = resources.loadTexture("assets/images/success.jpg"); - if (bgTex) { - auto background = extra2d::Sprite::create(bgTex); - float bgWidth = static_cast(bgTex->width()); - float bgHeight = static_cast(bgTex->height()); - float offsetX = (screenW - bgWidth) / 2.0f; - float offsetY = (screenH - bgHeight) / 2.0f; - - background->setAnchor(0.0f, 0.0f); - background->setPosition(offsetX, offsetY); - addChild(background); - - float centerX = screenW / 2.0f; - - auto font = loadMenuFont(); - if (font) { - // 创建按钮文本(仅显示,不响应鼠标) - auto backText = extra2d::Text::create("回主菜单", font); - backText->setPosition(centerX, offsetY + 350.0f); - backText->setTextColor(extra2d::Colors::Black); - addChild(backText); - - // 创建选择指示器(箭头) - selectorText_ = extra2d::Text::create(">", font); - selectorText_->setTextColor(extra2d::Colors::Red); - selectorText_->setPosition(centerX - 80.0f, offsetY + 350.0f); - addChild(selectorText_); - } - } - } -} - -void SuccessScene::onUpdate(float dt) { - BaseScene::onUpdate(dt); - - auto &app = extra2d::Application::instance(); - auto &input = app.input(); - - // A键确认返回主菜单 - if (input.isButtonPressed(extra2d::GamepadButton::A)) { - auto &scenes = extra2d::Application::instance().scenes(); - scenes.popScene(extra2d::TransitionType::Fade, 0.5f); - scenes.popScene(extra2d::TransitionType::Fade, 0.5f); - } -} - -} // namespace pushbox diff --git a/examples/push_box/SuccessScene.h b/examples/push_box/SuccessScene.h deleted file mode 100644 index 688c14c..0000000 --- a/examples/push_box/SuccessScene.h +++ /dev/null @@ -1,37 +0,0 @@ -// ============================================================================ -// SuccessScene.h - Push Box 通关场景 -// ============================================================================ - -#pragma once - -#include "BaseScene.h" -#include - -namespace pushbox { - -/** - * @brief Push Box 通关场景 - */ -class SuccessScene : public BaseScene { -public: - /** - * @brief 构造函数 - */ - SuccessScene(); - - /** - * @brief 场景进入时调用 - */ - void onEnter() override; - - /** - * @brief 每帧更新 - * @param dt 帧间隔时间 - */ - void onUpdate(float dt) override; - -private: - extra2d::Ptr selectorText_; -}; - -} // namespace pushbox diff --git a/examples/push_box/audio_manager.cpp b/examples/push_box/audio_manager.cpp deleted file mode 100644 index 92a8667..0000000 --- a/examples/push_box/audio_manager.cpp +++ /dev/null @@ -1,112 +0,0 @@ -#include "audio_manager.h" - -#include "storage.h" - -namespace pushbox { - -// ============================================================================ -// 单例实现 -// ============================================================================ -AudioManager& AudioManager::instance() { - static AudioManager instance; - return instance; -} - -// ============================================================================ -// 初始化音频资源 -// ============================================================================ -void AudioManager::init() { - if (initialized_) { - return; - } - - auto& resources = extra2d::Application::instance().resources(); - - // 加载音效资源 - background_ = resources.loadSound("pushbox_bg", "assets/audio/background.wav"); - manMove_ = resources.loadSound("pushbox_manmove", "assets/audio/manmove.wav"); - boxMove_ = resources.loadSound("pushbox_boxmove", "assets/audio/boxmove.wav"); - - // 设置背景音乐循环播放 - if (background_) { - background_->setLooping(true); - } - - // 从存储中读取音效设置 - enabled_ = g_SoundOpen; - - initialized_ = true; - - // 如果音效开启,播放背景音乐 - if (enabled_ && background_) { - background_->play(); - } -} - -// ============================================================================ -// 启用/禁用音效 -// ============================================================================ -void AudioManager::setEnabled(bool enabled) { - enabled_ = enabled; - g_SoundOpen = enabled; - saveSoundOpen(enabled); - - if (!background_) { - return; - } - - if (enabled_) { - background_->resume(); - } else { - background_->pause(); - } -} - -// ============================================================================ -// 播放角色移动音效 -// ============================================================================ -void AudioManager::playManMove() { - if (!enabled_ || !manMove_) { - return; - } - manMove_->play(); -} - -// ============================================================================ -// 播放箱子移动音效 -// ============================================================================ -void AudioManager::playBoxMove() { - if (!enabled_ || !boxMove_) { - return; - } - boxMove_->play(); -} - -// ============================================================================ -// 背景音乐控制 -// ============================================================================ -void AudioManager::playBackground() { - if (background_) { - background_->play(); - } -} - -void AudioManager::pauseBackground() { - if (background_) { - background_->pause(); - } -} - -void AudioManager::resumeBackground() { - if (background_) { - background_->resume(); - } -} - -void AudioManager::stopBackground() { - if (background_) { - background_->stop(); - } -} - -} // namespace pushbox diff --git a/examples/push_box/audio_manager.h b/examples/push_box/audio_manager.h deleted file mode 100644 index 5917443..0000000 --- a/examples/push_box/audio_manager.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include "data.h" -#include - -namespace pushbox { - -// ============================================================================ -// 全局音频管理器 - 单例模式,不依赖场景生命周期 -// ============================================================================ -class AudioManager { -public: - // 获取单例实例 - static AudioManager& instance(); - - // 初始化音频资源 - void init(); - - // 启用/禁用音效 - void setEnabled(bool enabled); - bool isEnabled() const { return enabled_; } - - // 播放音效 - void playManMove(); - void playBoxMove(); - - // 背景音乐控制 - void playBackground(); - void pauseBackground(); - void resumeBackground(); - void stopBackground(); - -private: - AudioManager() = default; - ~AudioManager() = default; - AudioManager(const AudioManager&) = delete; - AudioManager& operator=(const AudioManager&) = delete; - - bool initialized_ = false; - bool enabled_ = true; - - extra2d::Ptr background_; - extra2d::Ptr manMove_; - extra2d::Ptr boxMove_; -}; - -} // namespace pushbox diff --git a/examples/push_box/data.cpp b/examples/push_box/data.cpp deleted file mode 100644 index 33902f1..0000000 --- a/examples/push_box/data.cpp +++ /dev/null @@ -1,117 +0,0 @@ -#include "data.h" - -namespace pushbox { - -int g_CurrentLevel = 1; -bool g_SoundOpen = true; -int g_Direct = 2; -bool g_Pushing = false; - -Map g_Maps[MAX_LEVEL] = { - { - 8, 8, 4, 4, - { - {{Empty}, {Empty}, {Wall}, {Wall}, {Wall}, {Empty}, {Empty}, {Empty}}, - {{Empty}, {Empty}, {Wall}, {Ground, true}, {Wall}, {Empty}, {Empty}, {Empty}}, - {{Empty}, {Empty}, {Wall}, {Ground}, {Wall}, {Wall}, {Wall}, {Wall}}, - {{Wall}, {Wall}, {Wall}, {Box}, {Ground}, {Box}, {Ground, true}, {Wall}}, - {{Wall}, {Ground, true}, {Ground}, {Box}, {Man}, {Wall}, {Wall}, {Wall}}, - {{Wall}, {Wall}, {Wall}, {Wall}, {Box}, {Wall}, {Empty}, {Empty}}, - {{Empty}, {Empty}, {Empty}, {Wall}, {Ground, true}, {Wall}, {Empty}, {Empty}}, - {{Empty}, {Empty}, {Empty}, {Wall}, {Wall}, {Wall}, {Empty}, {Empty}}, - }, - }, - { - 9, 9, 1, 1, - { - {{Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Empty}, {Empty}, {Empty}, {Empty}}, - {{Wall}, {Man}, {Ground}, {Ground}, {Wall}, {Empty}, {Empty}, {Empty}, {Empty}}, - {{Wall}, {Ground}, {Box}, {Box}, {Wall}, {Empty}, {Wall}, {Wall}, {Wall}}, - {{Wall}, {Ground}, {Box}, {Ground}, {Wall}, {Empty}, {Wall}, {Ground, true}, {Wall}}, - {{Wall}, {Wall}, {Wall}, {Ground}, {Wall}, {Wall}, {Wall}, {Ground, true}, {Wall}}, - {{Empty}, {Wall}, {Wall}, {Ground}, {Ground}, {Ground}, {Ground}, {Ground, true}, {Wall}}, - {{Empty}, {Wall}, {Ground}, {Ground}, {Ground}, {Wall}, {Ground}, {Ground}, {Wall}}, - {{Empty}, {Wall}, {Ground}, {Ground}, {Ground}, {Wall}, {Wall}, {Wall}, {Wall}}, - {{Empty}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Empty}, {Empty}, {Empty}}, - }, - }, - { - 10, 7, 3, 3, - { - {{Empty}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Empty}, {Empty}}, - {{Empty}, {Wall}, {Ground}, {Ground}, {Ground}, {Ground}, {Ground}, {Wall}, {Wall}, {Wall}}, - {{Wall}, {Wall}, {Box}, {Wall}, {Wall}, {Wall}, {Ground}, {Ground}, {Ground}, {Wall}}, - {{Wall}, {Ground}, {Ground}, {Man}, {Box}, {Ground}, {Ground}, {Box}, {Ground}, {Wall}}, - {{Wall}, {Ground}, {Ground, true}, {Ground, true}, {Wall}, {Ground}, {Box}, {Ground}, {Wall}, {Wall}}, - {{Wall}, {Wall}, {Ground, true}, {Ground, true}, {Wall}, {Ground}, {Ground}, {Ground}, {Wall}, {Empty}}, - {{Empty}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Empty}}, - }, - }, - { - 6, 8, 1, 2, - { - {{Empty}, {Wall}, {Wall}, {Wall}, {Wall}, {Empty}}, - {{Wall}, {Wall}, {Ground}, {Ground}, {Wall}, {Empty}}, - {{Wall}, {Man}, {Box}, {Ground}, {Wall}, {Empty}}, - {{Wall}, {Wall}, {Box}, {Ground}, {Wall}, {Wall}}, - {{Wall}, {Wall}, {Ground}, {Box}, {Ground}, {Wall}}, - {{Wall}, {Ground, true}, {Box}, {Ground}, {Ground}, {Wall}}, - {{Wall}, {Ground, true}, {Ground, true}, {Box, true}, {Ground, true}, {Wall}}, - {{Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}}, - }, - }, - { - 8, 8, 2, 2, - { - {{Empty}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Empty}, {Empty}}, - {{Empty}, {Wall}, {Ground}, {Ground}, {Wall}, {Wall}, {Wall}, {Empty}}, - {{Empty}, {Wall}, {Man}, {Box}, {Ground}, {Ground}, {Wall}, {Empty}}, - {{Wall}, {Wall}, {Wall}, {Ground}, {Wall}, {Ground}, {Wall}, {Wall}}, - {{Wall}, {Ground, true}, {Wall}, {Ground}, {Wall}, {Ground}, {Ground}, {Wall}}, - {{Wall}, {Ground, true}, {Box}, {Ground}, {Ground}, {Wall}, {Ground}, {Wall}}, - {{Wall}, {Ground, true}, {Ground}, {Ground}, {Ground}, {Box}, {Ground}, {Wall}}, - {{Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}}, - }, - }, - { - 10, 8, 8, 1, - { - {{Empty}, {Empty}, {Empty}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}}, - {{Empty}, {Empty}, {Wall}, {Wall}, {Ground}, {Ground}, {Wall}, {Ground}, {Man}, {Wall}}, - {{Empty}, {Empty}, {Wall}, {Ground}, {Ground}, {Ground}, {Wall}, {Ground}, {Ground}, {Wall}}, - {{Empty}, {Empty}, {Wall}, {Box}, {Ground}, {Box}, {Ground}, {Box}, {Ground}, {Wall}}, - {{Empty}, {Empty}, {Wall}, {Ground}, {Box}, {Wall}, {Wall}, {Ground}, {Ground}, {Wall}}, - {{Wall}, {Wall}, {Wall}, {Ground}, {Box}, {Ground}, {Wall}, {Ground}, {Wall}, {Wall}}, - {{Wall}, {Ground, true}, {Ground, true}, {Ground, true}, {Ground, true}, {Ground, true}, {Ground}, {Ground}, {Wall}, {Empty}}, - {{Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Empty}}, - }, - }, - { - 10, 7, 8, 3, - { - {{Empty}, {Empty}, {Empty}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Empty}}, - {{Empty}, {Wall}, {Wall}, {Wall}, {Ground}, {Ground}, {Ground}, {Ground}, {Wall}, {Empty}}, - {{Wall}, {Wall}, {Ground, true}, {Ground}, {Box}, {Wall}, {Wall}, {Ground}, {Wall}, {Wall}}, - {{Wall}, {Ground, true}, {Ground, true}, {Box}, {Ground}, {Box}, {Ground}, {Ground}, {Man}, {Wall}}, - {{Wall}, {Ground, true}, {Ground, true}, {Ground}, {Box}, {Ground}, {Box}, {Ground}, {Wall}, {Wall}}, - {{Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Ground}, {Ground}, {Wall}, {Empty}}, - {{Empty}, {Empty}, {Empty}, {Empty}, {Empty}, {Wall}, {Wall}, {Wall}, {Wall}, {Empty}}, - }, - }, - { - 11, 9, 8, 7, - { - {{Empty}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Empty}}, - {{Empty}, {Wall}, {Ground}, {Ground}, {Wall}, {Wall}, {Ground}, {Ground}, {Ground}, {Wall}, {Empty}}, - {{Empty}, {Wall}, {Ground}, {Ground}, {Ground}, {Box}, {Ground}, {Ground}, {Ground}, {Wall}, {Empty}}, - {{Empty}, {Wall}, {Box}, {Ground}, {Wall}, {Wall}, {Wall}, {Ground}, {Box}, {Wall}, {Empty}}, - {{Empty}, {Wall}, {Ground}, {Wall}, {Ground, true}, {Ground, true}, {Ground, true}, {Wall}, {Ground}, {Wall}, {Empty}}, - {{Wall}, {Wall}, {Ground}, {Wall}, {Ground, true}, {Ground, true}, {Ground, true}, {Wall}, {Ground}, {Wall}, {Wall}}, - {{Wall}, {Ground}, {Box}, {Ground}, {Ground}, {Box}, {Ground}, {Ground}, {Box}, {Ground}, {Wall}}, - {{Wall}, {Ground}, {Ground}, {Ground}, {Ground}, {Ground}, {Wall}, {Ground}, {Man}, {Ground}, {Wall}}, - {{Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}, {Wall}}, - }, - }, -}; - -} // namespace pushbox diff --git a/examples/push_box/data.h b/examples/push_box/data.h deleted file mode 100644 index 5d3801f..0000000 --- a/examples/push_box/data.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#define MAX_LEVEL 8 -#define GAME_WIDTH 640.0f -#define GAME_HEIGHT 480.0f - -namespace pushbox { - -enum TYPE { Empty, Wall, Ground, Box, Man }; - -struct Piece { - TYPE type; - bool isPoint; -}; - -struct Map { - int width; - int height; - int roleX; - int roleY; - Piece value[12][12]; -}; - -/** - * @brief 移动记录 - 用于撤销功能(对象池示例) - * 这个结构体演示如何使用对象池管理小对象 - */ -struct MoveRecord { - int fromX, fromY; - int toX, toY; - int boxFromX, boxFromY; - int boxToX, boxToY; - bool pushedBox; - - MoveRecord() = default; - MoveRecord(int fx, int fy, int tx, int ty, bool pushed = false) - : fromX(fx), fromY(fy), toX(tx), toY(ty) - , boxFromX(-1), boxFromY(-1), boxToX(-1), boxToY(-1) - , pushedBox(pushed) {} -}; - -extern Map g_Maps[MAX_LEVEL]; -extern int g_CurrentLevel; -extern bool g_SoundOpen; -extern int g_Direct; -extern bool g_Pushing; - -} // namespace pushbox diff --git a/examples/push_box/main.cpp b/examples/push_box/main.cpp deleted file mode 100644 index fbeb7a7..0000000 --- a/examples/push_box/main.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include -#include "StartScene.h" -#include "data.h" -#include "storage.h" -#include "audio_manager.h" - -using namespace extra2d; - -// ============================================================================ -// 程序入口 -// ============================================================================ - -int main(int argc, char **argv) -{ - Logger::init(); - Logger::setLevel(LogLevel::Debug); - - E2D_LOG_INFO("========================"); - E2D_LOG_INFO("Extra2D push_box"); - E2D_LOG_INFO("========================"); - - auto &app = Application::instance(); - - AppConfig config; - config.title = "Extra2D - push_box"; - config.width = 1280; - config.height = 720; - config.vsync = true; - config.fpsLimit = 60; - - if (!app.init(config)) { - E2D_LOG_ERROR("应用初始化失败!"); - return -1; - } - - // 初始化存储系统 - // 在 Windows 上使用当前工作目录,在 Switch 上使用 sdmc:/ -#ifdef __SWITCH__ - pushbox::initStorage("sdmc:/"); -#else - pushbox::initStorage("."); -#endif - pushbox::g_CurrentLevel = pushbox::loadCurrentLevel(1); - if (pushbox::g_CurrentLevel > MAX_LEVEL) { - pushbox::g_CurrentLevel = 1; - } - pushbox::g_SoundOpen = pushbox::loadSoundOpen(true); - - // 初始化全局音频管理器 - pushbox::AudioManager::instance().init(); - - // 进入开始场景(主界面) - app.enterScene(makePtr()); - - E2D_LOG_INFO("开始主循环..."); - app.run(); - - E2D_LOG_INFO("应用结束"); - return 0; -} diff --git a/examples/push_box/romfs/assets/audio/background.wav b/examples/push_box/romfs/assets/audio/background.wav deleted file mode 100644 index 8b669c6..0000000 Binary files a/examples/push_box/romfs/assets/audio/background.wav and /dev/null differ diff --git a/examples/push_box/romfs/assets/audio/boxmove.wav b/examples/push_box/romfs/assets/audio/boxmove.wav deleted file mode 100644 index cda7cc0..0000000 Binary files a/examples/push_box/romfs/assets/audio/boxmove.wav and /dev/null differ diff --git a/examples/push_box/romfs/assets/audio/manmove.wav b/examples/push_box/romfs/assets/audio/manmove.wav deleted file mode 100644 index c13f1bd..0000000 Binary files a/examples/push_box/romfs/assets/audio/manmove.wav and /dev/null differ diff --git a/examples/push_box/romfs/assets/font.ttf b/examples/push_box/romfs/assets/font.ttf deleted file mode 100644 index 8997148..0000000 Binary files a/examples/push_box/romfs/assets/font.ttf and /dev/null differ diff --git a/examples/push_box/romfs/assets/images/box.gif b/examples/push_box/romfs/assets/images/box.gif deleted file mode 100644 index 4c835fd..0000000 Binary files a/examples/push_box/romfs/assets/images/box.gif and /dev/null differ diff --git a/examples/push_box/romfs/assets/images/boxinpoint.gif b/examples/push_box/romfs/assets/images/boxinpoint.gif deleted file mode 100644 index a12c967..0000000 Binary files a/examples/push_box/romfs/assets/images/boxinpoint.gif and /dev/null differ diff --git a/examples/push_box/romfs/assets/images/floor.gif b/examples/push_box/romfs/assets/images/floor.gif deleted file mode 100644 index 321d058..0000000 Binary files a/examples/push_box/romfs/assets/images/floor.gif and /dev/null differ diff --git a/examples/push_box/romfs/assets/images/player/mandown.gif b/examples/push_box/romfs/assets/images/player/mandown.gif deleted file mode 100644 index 98ccb72..0000000 Binary files a/examples/push_box/romfs/assets/images/player/mandown.gif and /dev/null differ diff --git a/examples/push_box/romfs/assets/images/player/manhanddown.gif b/examples/push_box/romfs/assets/images/player/manhanddown.gif deleted file mode 100644 index 6e1ba0f..0000000 Binary files a/examples/push_box/romfs/assets/images/player/manhanddown.gif and /dev/null differ diff --git a/examples/push_box/romfs/assets/images/player/manhandleft.gif b/examples/push_box/romfs/assets/images/player/manhandleft.gif deleted file mode 100644 index 18326bf..0000000 Binary files a/examples/push_box/romfs/assets/images/player/manhandleft.gif and /dev/null differ diff --git a/examples/push_box/romfs/assets/images/player/manhandright.gif b/examples/push_box/romfs/assets/images/player/manhandright.gif deleted file mode 100644 index e6e3618..0000000 Binary files a/examples/push_box/romfs/assets/images/player/manhandright.gif and /dev/null differ diff --git a/examples/push_box/romfs/assets/images/player/manhandup.gif b/examples/push_box/romfs/assets/images/player/manhandup.gif deleted file mode 100644 index 33e4a79..0000000 Binary files a/examples/push_box/romfs/assets/images/player/manhandup.gif and /dev/null differ diff --git a/examples/push_box/romfs/assets/images/player/manleft.gif b/examples/push_box/romfs/assets/images/player/manleft.gif deleted file mode 100644 index e875c28..0000000 Binary files a/examples/push_box/romfs/assets/images/player/manleft.gif and /dev/null differ diff --git a/examples/push_box/romfs/assets/images/player/manright.gif b/examples/push_box/romfs/assets/images/player/manright.gif deleted file mode 100644 index bf94f12..0000000 Binary files a/examples/push_box/romfs/assets/images/player/manright.gif and /dev/null differ diff --git a/examples/push_box/romfs/assets/images/player/manup.gif b/examples/push_box/romfs/assets/images/player/manup.gif deleted file mode 100644 index 7211b42..0000000 Binary files a/examples/push_box/romfs/assets/images/player/manup.gif and /dev/null differ diff --git a/examples/push_box/romfs/assets/images/point.gif b/examples/push_box/romfs/assets/images/point.gif deleted file mode 100644 index b81cbe4..0000000 Binary files a/examples/push_box/romfs/assets/images/point.gif and /dev/null differ diff --git a/examples/push_box/romfs/assets/images/soundoff.png b/examples/push_box/romfs/assets/images/soundoff.png deleted file mode 100644 index 246abd1..0000000 Binary files a/examples/push_box/romfs/assets/images/soundoff.png and /dev/null differ diff --git a/examples/push_box/romfs/assets/images/soundon.png b/examples/push_box/romfs/assets/images/soundon.png deleted file mode 100644 index 592edb4..0000000 Binary files a/examples/push_box/romfs/assets/images/soundon.png and /dev/null differ diff --git a/examples/push_box/romfs/assets/images/start.jpg b/examples/push_box/romfs/assets/images/start.jpg deleted file mode 100644 index 0a2e7e8..0000000 Binary files a/examples/push_box/romfs/assets/images/start.jpg and /dev/null differ diff --git a/examples/push_box/romfs/assets/images/success.jpg b/examples/push_box/romfs/assets/images/success.jpg deleted file mode 100644 index f12b5ac..0000000 Binary files a/examples/push_box/romfs/assets/images/success.jpg and /dev/null differ diff --git a/examples/push_box/romfs/assets/images/wall.gif b/examples/push_box/romfs/assets/images/wall.gif deleted file mode 100644 index 448dc5e..0000000 Binary files a/examples/push_box/romfs/assets/images/wall.gif and /dev/null differ diff --git a/examples/push_box/storage.cpp b/examples/push_box/storage.cpp deleted file mode 100644 index e219867..0000000 --- a/examples/push_box/storage.cpp +++ /dev/null @@ -1,92 +0,0 @@ -#include "storage.h" - -#include -#include - -namespace pushbox { - -static extra2d::DataStore g_store; -static std::filesystem::path g_filePath; -static bool g_loaded = false; - -static void ensureLoaded() { - if (g_loaded) { - return; - } - if (!g_filePath.empty()) { - g_store.load(g_filePath.string()); - } - g_loaded = true; -} - -void initStorage(const std::filesystem::path& baseDir) { - // Nintendo Switch 标准保存位置:/save/ 或 sdmc:/switch/ - // 优先使用 /save/ 目录(官方存档位置) - std::filesystem::path saveDir; - - // 检查是否存在 /save/ 目录(这是 NS 官方存档目录) - if (std::filesystem::exists("/save/")) { - saveDir = "/save/"; - } else if (std::filesystem::exists("/switch/")) { - // 备用:使用 /switch/ 目录 - saveDir = "/switch/push_box/"; - std::filesystem::create_directories(saveDir); - } else { - // 开发环境:使用 sdmc:/switch/ - saveDir = baseDir / "switch/push_box/"; - std::filesystem::create_directories(saveDir); - } - - g_filePath = saveDir / "pushbox.ini"; - g_store.load(g_filePath.string()); - g_loaded = true; -} - -int loadCurrentLevel(int defaultValue) { - ensureLoaded(); - int level = g_store.getInt("game", "level", defaultValue); - if (level < 1) { - level = 1; - } - return level; -} - -void saveCurrentLevel(int level) { - ensureLoaded(); - g_store.setInt("game", "level", level); - if (!g_filePath.empty()) { - g_store.save(g_filePath.string()); - } -} - -bool loadSoundOpen(bool defaultValue) { - ensureLoaded(); - return g_store.getBool("game", "sound", defaultValue); -} - -void saveSoundOpen(bool open) { - ensureLoaded(); - g_store.setBool("game", "sound", open); - if (!g_filePath.empty()) { - g_store.save(g_filePath.string()); - } -} - -int loadBestStep(int level, int defaultValue) { - ensureLoaded(); - std::string key = "level" + std::to_string(level); - return g_store.getInt("best", key, defaultValue); -} - -void saveBestStep(int level, int step) { - ensureLoaded(); - std::string key = "level" + std::to_string(level); - g_store.setInt("best", key, step); - if (!g_filePath.empty()) { - g_store.save(g_filePath.string()); - } -} - -std::filesystem::path storageFilePath() { return g_filePath; } - -} // namespace pushbox diff --git a/examples/push_box/storage.h b/examples/push_box/storage.h deleted file mode 100644 index d766a8a..0000000 --- a/examples/push_box/storage.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include - -namespace pushbox { - -void initStorage(const std::filesystem::path& baseDir); - -int loadCurrentLevel(int defaultValue = 1); -void saveCurrentLevel(int level); - -bool loadSoundOpen(bool defaultValue = true); -void saveSoundOpen(bool open); - -int loadBestStep(int level, int defaultValue = 0); -void saveBestStep(int level, int step); - -std::filesystem::path storageFilePath(); - -} // namespace pushbox diff --git a/examples/push_box/xmake.lua b/examples/push_box/xmake.lua deleted file mode 100644 index 6c671c5..0000000 --- a/examples/push_box/xmake.lua +++ /dev/null @@ -1,76 +0,0 @@ --- ============================================== --- Push Box 示例 - Xmake 构建脚本 --- 支持平台: MinGW (Windows), Nintendo Switch --- ============================================== - --- 获取当前脚本所在目录(示例根目录) -local example_dir = os.scriptdir() - --- 可执行文件目标 -target("push_box") - set_kind("binary") - add_files("*.cpp") - add_includedirs("../../include") - add_deps("extra2d") - - -- 使用与主项目相同的平台配置 - if is_plat("switch") then - set_targetdir("../../build/examples/push_box") - - -- 构建后生成 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, "push_box.nacp") - local nro_file = path.join(output_dir, "push_box.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", "Push Box", "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(), "push_box.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/push_box") - -- 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/spatial_index_demo/main.cpp b/examples/spatial_index_demo/main.cpp deleted file mode 100644 index b366d48..0000000 --- a/examples/spatial_index_demo/main.cpp +++ /dev/null @@ -1,538 +0,0 @@ -#include -#include - -using namespace extra2d; - -// ============================================================================ -// 性能统计 -// ============================================================================ -struct PerformanceStats { - double updateTime = 0.0; - double collisionTime = 0.0; - double renderTime = 0.0; - size_t collisionCount = 0; - size_t nodeCount = 0; - const char *strategyName = "Unknown"; -}; - -// ============================================================================ -// 碰撞节点 - 使用引擎自带的空间索引功能 -// ============================================================================ -class PhysicsNode : public Node { -public: - PhysicsNode(float size, const Color &color, int id) - : size_(size), color_(color), id_(id), isColliding_(false) { - // 启用引擎自带的空间索引功能 - // 这是关键:设置 spatialIndexed_ = true 让节点参与空间索引 - setSpatialIndexed(true); - - // 随机速度 - std::random_device rd; - std::mt19937 gen(rd() + id); - std::uniform_real_distribution velDist(-150.0f, 150.0f); - velocity_ = Vec2(velDist(gen), velDist(gen)); - } - - void setColliding(bool colliding) { isColliding_ = colliding; } - bool isColliding() const { return isColliding_; } - int getId() const { return id_; } - - // 必须实现 boundingBox() 才能参与空间索引碰撞检测 - Rect boundingBox() const override { - Vec2 position = pos(); - return Rect(position.x - size_ / 2, position.y - size_ / 2, size_, size_); - } - - void update(float dt, float screenWidth, float screenHeight) { - Vec2 position = pos(); - position = position + velocity_ * dt; - - // 边界反弹 - if (position.x < size_ / 2 || position.x > screenWidth - size_ / 2) { - velocity_.x = -velocity_.x; - position.x = std::clamp(position.x, size_ / 2, screenWidth - size_ / 2); - } - if (position.y < size_ / 2 || position.y > screenHeight - size_ / 2) { - velocity_.y = -velocity_.y; - position.y = std::clamp(position.y, size_ / 2, screenHeight - size_ / 2); - } - - setPosition(position); - } - - void onRender(Renderer &renderer) override { - Vec2 position = pos(); - - // 碰撞时变红色 - Color fillColor = isColliding_ ? Color(1.0f, 0.2f, 0.2f, 0.9f) : color_; - renderer.fillRect( - Rect(position.x - size_ / 2, position.y - size_ / 2, size_, size_), - fillColor); - - // 绘制边框 - Color borderColor = isColliding_ ? Color(1.0f, 0.0f, 0.0f, 1.0f) - : Color(0.3f, 0.3f, 0.3f, 0.5f); - renderer.drawRect( - Rect(position.x - size_ / 2, position.y - size_ / 2, size_, size_), - borderColor, 1.0f); - } - -private: - float size_; - Color color_; - int id_; - bool isColliding_; - Vec2 velocity_; -}; - -// ============================================================================ -// 空间索引演示场景 -// ============================================================================ -class SpatialIndexDemoScene : public Scene { -public: - void onEnter() override { - // 必须先调用父类的 onEnter(),这样才能正确设置 running_ 状态 - // 并触发子节点的 onAttachToScene,将节点注册到空间索引 - Scene::onEnter(); - - E2D_LOG_INFO("SpatialIndexDemoScene::onEnter - 引擎空间索引演示"); - - auto &app = Application::instance(); - screenWidth_ = static_cast(app.getConfig().width); - screenHeight_ = static_cast(app.getConfig().height); - - // 设置背景色 - setBackgroundColor(Color(0.05f, 0.05f, 0.1f, 1.0f)); - - // 创建100个碰撞节点 - createPhysicsNodes(100); - - // 加载字体 - loadFonts(); - - E2D_LOG_INFO("空间索引已启用: {}", isSpatialIndexingEnabled()); - } - - void onExit() override { - // 显式移除所有子节点,确保在场景析构前正确清理空间索引 - // 这必须在 Scene::onExit() 之前调用,因为 onExit() 会将 running_ 设为 false - removeAllChildren(); - - Scene::onExit(); - } - - void onUpdate(float dt) override { - Scene::onUpdate(dt); - - auto startTime = std::chrono::high_resolution_clock::now(); - - // 更新所有物理节点位置 - for (const auto &child : children()) { - if (auto node = dynamic_cast(child.get())) { - node->update(dt, screenWidth_, screenHeight_); - } - } - - auto updateEndTime = std::chrono::high_resolution_clock::now(); - stats_.updateTime = - std::chrono::duration(updateEndTime - startTime) - .count(); - - // 使用引擎自带的空间索引进行碰撞检测 - performCollisionDetection(); - - auto collisionEndTime = std::chrono::high_resolution_clock::now(); - stats_.collisionTime = std::chrono::duration( - collisionEndTime - updateEndTime) - .count(); - - // 统计物理节点数量 - stats_.nodeCount = getPhysicsNodeCount(); - - // 获取当前使用的空间索引策略 - stats_.strategyName = getSpatialManager().getStrategyName(); - - // 检查退出按键 - auto &input = Application::instance().input(); - if (input.isButtonPressed(GamepadButton::Start)) { - E2D_LOG_INFO("退出应用"); - Application::instance().quit(); - } - - // 按 A 键添加节点 - if (input.isButtonPressed(GamepadButton::A)) { - addNodes(100); - } - - // 按 B 键减少节点 - if (input.isButtonPressed(GamepadButton::B)) { - removeNodes(100); - } - - // 按 X 键切换空间索引策略 - if (input.isButtonPressed(GamepadButton::X)) { - toggleSpatialStrategy(); - } - } - - void onRender(Renderer &renderer) override { - auto renderStart = std::chrono::high_resolution_clock::now(); - - Scene::onRender(renderer); - - auto renderEnd = std::chrono::high_resolution_clock::now(); - stats_.renderTime = - std::chrono::duration(renderEnd - renderStart) - .count(); - - // 更新UI文本内容 - updateUI(); - - // 绘制图例方块(Text组件会自动渲染) - drawLegend(renderer); - } - -private: - /** - * @brief 加载字体资源并创建UI文本组件 - */ - void loadFonts() { - auto &resources = Application::instance().resources(); - - titleFont_ = resources.loadFont("assets/font.ttf", 28, true); - infoFont_ = resources.loadFont("assets/font.ttf", 16, true); - - // 创建标题文本 - titleText_ = Text::create("引擎空间索引演示", titleFont_); - titleText_->setPosition(30.0f, 20.0f); - titleText_->setTextColor(Color(1.0f, 1.0f, 1.0f, 1.0f)); - addChild(titleText_); - - float x = 30.0f; - float y = 60.0f; - float lineHeight = 22.0f; - - // 创建统计信息文本 - nodeCountText_ = Text::create("", infoFont_); - nodeCountText_->setPosition(x, y); - nodeCountText_->setTextColor(Color(0.9f, 0.9f, 0.9f, 1.0f)); - addChild(nodeCountText_); - y += lineHeight; - - strategyText_ = Text::create("", infoFont_); - strategyText_->setPosition(x, y); - strategyText_->setTextColor(Color(0.5f, 1.0f, 0.5f, 1.0f)); - addChild(strategyText_); - y += lineHeight; - - collisionText_ = Text::create("", infoFont_); - collisionText_->setPosition(x, y); - collisionText_->setTextColor(Color(1.0f, 0.5f, 0.5f, 1.0f)); - addChild(collisionText_); - y += lineHeight; - - updateTimeText_ = Text::create("", infoFont_); - updateTimeText_->setPosition(x, y); - updateTimeText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f)); - addChild(updateTimeText_); - y += lineHeight; - - collisionTimeText_ = Text::create("", infoFont_); - collisionTimeText_->setPosition(x, y); - collisionTimeText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f)); - addChild(collisionTimeText_); - y += lineHeight; - - renderTimeText_ = Text::create("", infoFont_); - renderTimeText_->setPosition(x, y); - renderTimeText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f)); - addChild(renderTimeText_); - y += lineHeight; - - fpsText_ = Text::create("", infoFont_); - fpsText_->setPosition(x, y); - fpsText_->setTextColor(Color(0.5f, 1.0f, 0.5f, 1.0f)); - addChild(fpsText_); - y += lineHeight * 1.5f; - - // 创建操作说明文本 - helpTitleText_ = Text::create("操作说明:", infoFont_); - helpTitleText_->setPosition(x, y); - helpTitleText_->setTextColor(Color(1.0f, 1.0f, 0.5f, 1.0f)); - addChild(helpTitleText_); - y += lineHeight; - - helpAddText_ = Text::create("A键 - 添加100个节点", infoFont_); - helpAddText_->setPosition(x + 10, y); - helpAddText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f)); - addChild(helpAddText_); - y += lineHeight; - - helpRemoveText_ = Text::create("B键 - 移除100个节点", infoFont_); - helpRemoveText_->setPosition(x + 10, y); - helpRemoveText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f)); - addChild(helpRemoveText_); - y += lineHeight; - - helpToggleText_ = Text::create("X键 - 切换索引策略", infoFont_); - helpToggleText_->setPosition(x + 10, y); - helpToggleText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f)); - addChild(helpToggleText_); - y += lineHeight; - - helpExitText_ = Text::create("+键 - 退出程序", infoFont_); - helpExitText_->setPosition(x + 10, y); - helpExitText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f)); - addChild(helpExitText_); - - // 创建图例文本 - float legendX = screenWidth_ - 200.0f; - float legendY = 20.0f; - - legendTitleText_ = Text::create("图例:", infoFont_); - legendTitleText_->setPosition(legendX, legendY); - legendTitleText_->setTextColor(Color(1.0f, 1.0f, 1.0f, 1.0f)); - addChild(legendTitleText_); - legendY += 25.0f; - - legendNormalText_ = Text::create("- 正常", infoFont_); - legendNormalText_->setPosition(legendX + 20.0f, legendY); - legendNormalText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f)); - addChild(legendNormalText_); - legendY += 25.0f; - - legendCollidingText_ = Text::create("- 碰撞中", infoFont_); - legendCollidingText_->setPosition(legendX + 20.0f, legendY); - legendCollidingText_->setTextColor(Color(0.8f, 0.8f, 0.8f, 1.0f)); - addChild(legendCollidingText_); - } - - /** - * @brief 创建指定数量的物理节点 - */ - void createPhysicsNodes(size_t count) { - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_real_distribution posX(50.0f, screenWidth_ - 50.0f); - std::uniform_real_distribution posY(50.0f, screenHeight_ - 50.0f); - std::uniform_real_distribution colorR(0.2f, 0.9f); - std::uniform_real_distribution colorG(0.2f, 0.9f); - std::uniform_real_distribution colorB(0.2f, 0.9f); - - for (size_t i = 0; i < count; ++i) { - Color color(colorR(gen), colorG(gen), colorB(gen), 0.7f); - auto node = makePtr(20.0f, color, static_cast(i)); - node->setPosition(Vec2(posX(gen), posY(gen))); - addChild(node); - } - } - - /** - * @brief 获取物理节点数量 - */ - size_t getPhysicsNodeCount() const { - size_t count = 0; - for (const auto &child : children()) { - if (dynamic_cast(child.get())) { - ++count; - } - } - return count; - } - - /** - * @brief 获取所有物理节点 - */ - std::vector getPhysicsNodes() const { - std::vector nodes; - for (const auto &child : children()) { - if (auto node = dynamic_cast(child.get())) { - nodes.push_back(node); - } - } - return nodes; - } - - /** - * @brief 添加节点 - */ - void addNodes(size_t count) { - size_t currentCount = getPhysicsNodeCount(); - if (currentCount + count > 5000) { - E2D_LOG_WARN("节点数量已达上限(5000)"); - return; - } - createPhysicsNodes(count); - E2D_LOG_INFO("添加 {} 个节点,当前总数: {}", count, getPhysicsNodeCount()); - } - - /** - * @brief 移除节点 - */ - void removeNodes(size_t count) { - auto physicsNodes = getPhysicsNodes(); - if (count >= physicsNodes.size()) { - count = physicsNodes.size(); - } - if (count == 0) - return; - - // 从后往前移除指定数量的节点 - for (size_t i = 0; i < count; ++i) { - // 找到最后一个物理节点对应的子节点并移除 - for (auto it = children().rbegin(); it != children().rend(); ++it) { - if (dynamic_cast(it->get())) { - removeChild(*it); - break; - } - } - } - E2D_LOG_INFO("移除 {} 个节点,当前总数: {}", count, getPhysicsNodeCount()); - } - - /** - * @brief 切换空间索引策略 - */ - void toggleSpatialStrategy() { - auto &spatialManager = getSpatialManager(); - SpatialStrategy currentStrategy = spatialManager.getCurrentStrategy(); - - if (currentStrategy == SpatialStrategy::QuadTree) { - spatialManager.setStrategy(SpatialStrategy::SpatialHash); - E2D_LOG_INFO("切换到空间哈希策略"); - } else { - spatialManager.setStrategy(SpatialStrategy::QuadTree); - E2D_LOG_INFO("切换到四叉树策略"); - } - } - - /** - * @brief 使用引擎自带的空间索引进行碰撞检测 - * - * 关键方法: - * - Scene::queryCollisions() - 查询场景中所有碰撞的节点对 - * - SpatialManager::queryCollisions() - 空间管理器的碰撞检测 - */ - void performCollisionDetection() { - // 清除之前的碰撞状态 - for (const auto &child : children()) { - if (auto node = dynamic_cast(child.get())) { - node->setColliding(false); - } - } - - // 使用引擎自带的空间索引进行碰撞检测 - // 这是核心:Scene::queryCollisions() 会自动使用 SpatialManager - auto collisions = queryCollisions(); - - stats_.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); - } - } - } - - /** - * @brief 更新UI文本内容 - */ - void updateUI() { - if (!nodeCountText_) - return; - - auto &app = Application::instance(); - - // 使用 setFormat 格式化文本 - nodeCountText_->setFormat("节点数量: %zu", stats_.nodeCount); - strategyText_->setFormat("索引策略: %s", stats_.strategyName); - collisionText_->setFormat("碰撞对数: %zu", stats_.collisionCount); - updateTimeText_->setFormat("更新时间: %.2f ms", stats_.updateTime); - collisionTimeText_->setFormat("碰撞检测: %.2f ms", stats_.collisionTime); - renderTimeText_->setFormat("渲染时间: %.2f ms", stats_.renderTime); - fpsText_->setFormat("FPS: %u", app.fps()); - } - - /** - * @brief 绘制图例方块 - */ - void drawLegend(Renderer &renderer) { - float legendX = screenWidth_ - 200.0f; - float legendY = 20.0f + 25.0f; // 在标题下方 - - // 绘制正常状态方块 - renderer.fillRect(Rect(legendX, legendY, 15.0f, 15.0f), - Color(0.5f, 0.5f, 0.9f, 0.7f)); - legendY += 25.0f; - - // 绘制碰撞状态方块 - renderer.fillRect(Rect(legendX, legendY, 15.0f, 15.0f), - Color(1.0f, 0.2f, 0.2f, 0.9f)); - } - - PerformanceStats stats_; - float screenWidth_ = 1280.0f; - float screenHeight_ = 720.0f; - - Ptr titleFont_; - Ptr infoFont_; - - // UI 文本组件 - Ptr titleText_; - Ptr nodeCountText_; - Ptr strategyText_; - Ptr collisionText_; - Ptr updateTimeText_; - Ptr collisionTimeText_; - Ptr renderTimeText_; - Ptr fpsText_; - Ptr helpTitleText_; - Ptr helpAddText_; - Ptr helpRemoveText_; - Ptr helpToggleText_; - Ptr helpExitText_; - Ptr legendTitleText_; - Ptr legendNormalText_; - Ptr legendCollidingText_; -}; - -// ============================================================================ -// 程序入口 -// ============================================================================ - -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(makePtr()); - - E2D_LOG_INFO("开始主循环..."); - - app.run(); - - E2D_LOG_INFO("应用结束"); - - return 0; -} diff --git a/examples/spatial_index_demo/romfs/assets/font.ttf b/examples/spatial_index_demo/romfs/assets/font.ttf deleted file mode 100644 index 8997148..0000000 Binary files a/examples/spatial_index_demo/romfs/assets/font.ttf and /dev/null differ diff --git a/examples/spatial_index_demo/xmake.lua b/examples/spatial_index_demo/xmake.lua deleted file mode 100644 index 4d09d5c..0000000 --- a/examples/spatial_index_demo/xmake.lua +++ /dev/null @@ -1,76 +0,0 @@ --- ============================================== --- Spatial Index Demo 示例 - Xmake 构建脚本 --- 支持平台: MinGW (Windows), Nintendo Switch --- ============================================== - --- 获取当前脚本所在目录(示例根目录) -local example_dir = os.scriptdir() - --- 可执行文件目标 -target("spatial_index_demo") - set_kind("binary") - add_files("main.cpp") - add_includedirs("../../include") - add_deps("extra2d") - - -- 使用与主项目相同的平台配置 - if is_plat("switch") then - set_targetdir("../../build/examples/spatial_index_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, "spatial_index_demo.nacp") - local nro_file = path.join(output_dir, "spatial_index_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", "Spatial Index 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(), "spatial_index_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/spatial_index_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/include/extra2d.h b/include/extra2d.h index 006c8d7..82159ca 100644 --- a/include/extra2d.h +++ b/include/extra2d.h @@ -71,12 +71,6 @@ #include #include -// Spatial -#include -#include -#include -#include - // Application #include diff --git a/include/scene/node.h b/include/scene/node.h index 71cf3a9..238068d 100644 --- a/include/scene/node.h +++ b/include/scene/node.h @@ -146,17 +146,10 @@ public: virtual void onDetachFromScene(); // ------------------------------------------------------------------------ - // 边界框(用于空间索引) + // 边界框 // ------------------------------------------------------------------------ virtual Rect boundingBox() const; - // 是否需要参与空间索引(默认 true) - void setSpatialIndexed(bool indexed) { spatialIndexed_ = indexed; } - bool isSpatialIndexed() const { return spatialIndexed_; } - - // 更新空间索引(手动调用,通常在边界框变化后) - void updateSpatialIndex(); - // ------------------------------------------------------------------------ // 事件系统 // ------------------------------------------------------------------------ @@ -300,10 +293,7 @@ private: Vec2 anchor_ = Vec2(0.5f, 0.5f); // 8 bytes Vec2 skew_ = Vec2::Zero(); // 8 bytes - // 8. 边界框(用于空间索引) - Rect lastSpatialBounds_; // 16 bytes - - // 9. 浮点属性 + // 8. 浮点属性 float rotation_ = 0.0f; // 4 bytes float opacity_ = 1.0f; // 4 bytes @@ -321,15 +311,14 @@ private: // 11. 场景指针 Scene *scene_ = nullptr; // 8 bytes - // 12. 布尔标志(打包在一起) + // 9. 布尔标志(打包在一起) mutable bool transformDirty_ = true; // 1 byte mutable bool worldTransformDirty_ = true; // 1 byte bool childrenOrderDirty_ = false; // 1 byte bool visible_ = true; // 1 byte bool running_ = false; // 1 byte - bool spatialIndexed_ = true; // 1 byte - // 13. Tween 动画列表 + // 10. Tween 动画列表 std::vector> tweens_; }; diff --git a/include/scene/scene.h b/include/scene/scene.h index 2036895..d18ac87 100644 --- a/include/scene/scene.h +++ b/include/scene/scene.h @@ -3,7 +3,6 @@ #include #include #include -#include #include namespace extra2d { @@ -61,28 +60,6 @@ public: void collectRenderCommands(std::vector &commands, int parentZOrder = 0) override; - // ------------------------------------------------------------------------ - // 空间索引系统 - // ------------------------------------------------------------------------ - SpatialManager &getSpatialManager() { return spatialManager_; } - const SpatialManager &getSpatialManager() const { return spatialManager_; } - - // 启用/禁用空间索引 - void setSpatialIndexingEnabled(bool enabled) { - spatialIndexingEnabled_ = enabled; - } - bool isSpatialIndexingEnabled() const { return spatialIndexingEnabled_; } - - // 节点空间索引管理(内部使用) - void updateNodeInSpatialIndex(Node *node, const Rect &oldBounds, - const Rect &newBounds); - void removeNodeFromSpatialIndex(Node *node); - - // 碰撞检测查询 - std::vector queryNodesInArea(const Rect &area) const; - std::vector queryNodesAtPoint(const Vec2 &point) const; - std::vector> queryCollisions() const; - // ------------------------------------------------------------------------ // 静态创建方法 // ------------------------------------------------------------------------ @@ -107,10 +84,6 @@ private: Ptr defaultCamera_; bool paused_ = false; - - // 空间索引系统 - SpatialManager spatialManager_; - bool spatialIndexingEnabled_ = true; }; } // namespace extra2d diff --git a/include/spatial/quadtree.h b/include/spatial/quadtree.h deleted file mode 100644 index 5b1cbe5..0000000 --- a/include/spatial/quadtree.h +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once - -#include -#include - -namespace extra2d { - -class QuadTree : public ISpatialIndex { -public: - static constexpr int MAX_OBJECTS = 10; - static constexpr int MAX_LEVELS = 5; - - struct QuadTreeNode { - Rect bounds; - int level; - std::vector> objects; - std::array, 4> children; - - QuadTreeNode(const Rect &bounds, int level); - bool contains(const Rect &rect) const; - bool intersects(const Rect &rect) const; - }; - - explicit QuadTree(const Rect &worldBounds); - ~QuadTree() override = default; - - void insert(Node *node, const Rect &bounds) override; - void remove(Node *node) override; - void update(Node *node, const Rect &newBounds) override; - - std::vector query(const Rect &area) const override; - std::vector query(const Vec2 &point) const override; - std::vector> queryCollisions() const override; - - void clear() override; - size_t size() const override; - bool empty() const override; - - void rebuild() override; - -private: - void split(QuadTreeNode *node); - void insertIntoNode(QuadTreeNode *node, Node *object, const Rect &bounds); - void queryNode(const QuadTreeNode *node, const Rect &area, - std::vector &results) const; - void queryNode(const QuadTreeNode *node, const Vec2 &point, - std::vector &results) const; - void - collectCollisions(const QuadTreeNode *node, - std::vector> &collisions) const; - bool removeFromNode(QuadTreeNode *node, Node *object); - - /** - * @brief 使用扫描线算法检测节点内对象的碰撞 - * @param objects 对象列表 - * @param collisions 输出碰撞对 - */ - void detectCollisionsInNode( - const std::vector> &objects, - std::vector> &collisions) const; - - std::unique_ptr root_; - Rect worldBounds_; - size_t objectCount_ = 0; - - // 碰撞检测用的临时缓冲区,避免重复分配 - mutable std::vector> collisionBuffer_; -}; - -} // namespace extra2d diff --git a/include/spatial/spatial_hash.h b/include/spatial/spatial_hash.h deleted file mode 100644 index bfc7eae..0000000 --- a/include/spatial/spatial_hash.h +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace extra2d { - -/** - * @brief 空间哈希实现 - 优化内存布局版本 - * 使用连续内存存储单元格内容,减少内存碎片 - */ -class SpatialHash : public ISpatialIndex { -public: - using CellKey = std::pair; - - struct CellKeyHash { - size_t operator()(const CellKey &key) const { - return std::hash()(key.first) ^ - (std::hash()(key.second) << 1); - } - }; - - explicit SpatialHash(float cellSize = 64.0f); - ~SpatialHash() override = default; - - void insert(Node *node, const Rect &bounds) override; - void remove(Node *node) override; - void update(Node *node, const Rect &newBounds) override; - - std::vector query(const Rect &area) const override; - std::vector query(const Vec2 &point) const override; - std::vector> queryCollisions() const override; - - void clear() override; - size_t size() const override; - bool empty() const override; - - void rebuild() override; - - void setCellSize(float cellSize); - float getCellSize() const { return cellSize_; } - -private: - /** - * @brief 单元格数据 - 使用vector代替unordered_set减少内存开销 - */ - struct Cell { - std::vector objects; - - void insert(Node *node); - void remove(Node *node); - bool contains(Node *node) const; - void clear() { objects.clear(); } - size_t size() const { return objects.size(); } - bool empty() const { return objects.empty(); } - }; - - CellKey getCellKey(float x, float y) const; - void getCellsForRect(const Rect &rect, std::vector &cells) const; - void insertIntoCells(Node *node, const Rect &bounds); - void removeFromCells(Node *node, const Rect &bounds); - - float cellSize_; - // 使用vector存储对象列表代替unordered_set,内存更紧凑 - std::unordered_map grid_; - std::unordered_map objectBounds_; - size_t objectCount_ = 0; - - // 查询用的临时缓冲区,避免重复分配 - mutable std::vector queryBuffer_; - mutable std::vector> collisionBuffer_; -}; - -} // namespace extra2d diff --git a/include/spatial/spatial_index.h b/include/spatial/spatial_index.h deleted file mode 100644 index 06ae8af..0000000 --- a/include/spatial/spatial_index.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace extra2d { - -class Node; - -enum class SpatialStrategy { Auto, QuadTree, SpatialHash }; - -struct SpatialQueryResult { - Node *node; - Rect bounds; -}; - -class ISpatialIndex { -public: - virtual ~ISpatialIndex() = default; - - virtual void insert(Node *node, const Rect &bounds) = 0; - virtual void remove(Node *node) = 0; - virtual void update(Node *node, const Rect &newBounds) = 0; - - virtual std::vector query(const Rect &area) const = 0; - virtual std::vector query(const Vec2 &point) const = 0; - virtual std::vector> queryCollisions() const = 0; - - virtual void clear() = 0; - virtual size_t size() const = 0; - virtual bool empty() const = 0; - - virtual void rebuild() = 0; -}; - -using SpatialIndexPtr = std::unique_ptr; - -} // namespace extra2d diff --git a/include/spatial/spatial_manager.h b/include/spatial/spatial_manager.h deleted file mode 100644 index 496c11f..0000000 --- a/include/spatial/spatial_manager.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace extra2d { - -class SpatialManager { -public: - using QueryCallback = std::function; - - SpatialManager(); - explicit SpatialManager(const Rect &worldBounds); - ~SpatialManager() = default; - - void setStrategy(SpatialStrategy strategy); - void setAutoThresholds(size_t quadTreeThreshold, size_t hashThreshold); - - void setWorldBounds(const Rect &bounds); - Rect getWorldBounds() const { return worldBounds_; } - - void insert(Node *node, const Rect &bounds); - void remove(Node *node); - void update(Node *node, const Rect &newBounds); - - std::vector query(const Rect &area) const; - std::vector query(const Vec2 &point) const; - std::vector> queryCollisions() const; - - void query(const Rect &area, const QueryCallback &callback) const; - void query(const Vec2 &point, const QueryCallback &callback) const; - - void clear(); - size_t size() const; - bool empty() const; - - void rebuild(); - void optimize(); - - SpatialStrategy getCurrentStrategy() const; - const char *getStrategyName() const; - - static std::unique_ptr createIndex(SpatialStrategy strategy, - const Rect &bounds); - -private: - void selectOptimalStrategy(); - - SpatialStrategy currentStrategy_ = SpatialStrategy::Auto; - SpatialStrategy activeStrategy_ = SpatialStrategy::QuadTree; - std::unique_ptr index_; - Rect worldBounds_; - - size_t quadTreeThreshold_ = 1000; - size_t hashThreshold_ = 5000; - - mutable size_t queryCount_ = 0; - mutable size_t totalQueryTime_ = 0; -}; - -} // namespace extra2d diff --git a/include/ui/text.h b/include/ui/text.h index cb8f152..6729c9b 100644 --- a/include/ui/text.h +++ b/include/ui/text.h @@ -62,7 +62,6 @@ public: va_end(args); text_ = buffer; sizeDirty_ = true; - updateSpatialIndex(); } // ------------------------------------------------------------------------ diff --git a/src/scene/node.cpp b/src/scene/node.cpp index 23ab700..6471779 100644 --- a/src/scene/node.cpp +++ b/src/scene/node.cpp @@ -157,7 +157,6 @@ Ptr Node::childByTag(int tag) const { void Node::setPosition(const Vec2 &pos) { position_ = pos; markTransformDirty(); - updateSpatialIndex(); } void Node::setPosition(float x, float y) { setPosition(Vec2(x, y)); } @@ -165,13 +164,11 @@ void Node::setPosition(float x, float y) { setPosition(Vec2(x, y)); } void Node::setRotation(float degrees) { rotation_ = degrees; markTransformDirty(); - updateSpatialIndex(); } void Node::setScale(const Vec2 &scale) { scale_ = scale; markTransformDirty(); - updateSpatialIndex(); } void Node::setScale(float scale) { setScale(Vec2(scale, scale)); } @@ -356,26 +353,12 @@ void Node::onRender(Renderer &renderer) { void Node::onAttachToScene(Scene *scene) { scene_ = scene; - // 添加到场景的空间索引 - if (spatialIndexed_ && scene_) { - lastSpatialBounds_ = Rect(); - updateSpatialIndex(); - } - for (auto &child : children_) { child->onAttachToScene(scene); } } void Node::onDetachFromScene() { - // 从场景的空间索引移除 - // 注意:即使 lastSpatialBounds_ 为空也要尝试移除, - // 因为节点可能通过其他方式被插入到空间索引中 - if (spatialIndexed_ && scene_) { - scene_->removeNodeFromSpatialIndex(this); - lastSpatialBounds_ = Rect(); - } - scene_ = nullptr; for (auto &child : children_) { child->onDetachFromScene(); @@ -387,18 +370,6 @@ Rect Node::boundingBox() const { return Rect(position_.x, position_.y, 0, 0); } -void Node::updateSpatialIndex() { - if (!spatialIndexed_ || !scene_) { - return; - } - - Rect newBounds = boundingBox(); - if (newBounds != lastSpatialBounds_) { - scene_->updateNodeInSpatialIndex(this, lastSpatialBounds_, newBounds); - lastSpatialBounds_ = newBounds; - } -} - void Node::update(float dt) { onUpdate(dt); } void Node::render(Renderer &renderer) { diff --git a/src/scene/scene.cpp b/src/scene/scene.cpp index f588dc8..913f9dd 100644 --- a/src/scene/scene.cpp +++ b/src/scene/scene.cpp @@ -58,66 +58,12 @@ void Scene::updateScene(float dt) { void Scene::onEnter() { Node::onEnter(); - - // 初始化空间索引世界边界 - if (spatialIndexingEnabled_) { - spatialManager_.setWorldBounds( - Rect(0, 0, viewportSize_.width, viewportSize_.height)); - } } void Scene::onExit() { - // 清理空间索引 - spatialManager_.clear(); Node::onExit(); } -void Scene::updateNodeInSpatialIndex(Node *node, const Rect &oldBounds, - const Rect &newBounds) { - if (!spatialIndexingEnabled_ || !node || !node->isSpatialIndexed()) { - return; - } - - // 如果旧边界有效,先移除 - if (!oldBounds.empty()) { - spatialManager_.remove(node); - } - - // 如果新边界有效,插入 - if (!newBounds.empty()) { - spatialManager_.insert(node, newBounds); - } -} - -void Scene::removeNodeFromSpatialIndex(Node *node) { - if (!spatialIndexingEnabled_ || !node) { - return; - } - - spatialManager_.remove(node); -} - -std::vector Scene::queryNodesInArea(const Rect &area) const { - if (!spatialIndexingEnabled_) { - return {}; - } - return spatialManager_.query(area); -} - -std::vector Scene::queryNodesAtPoint(const Vec2 &point) const { - if (!spatialIndexingEnabled_) { - return {}; - } - return spatialManager_.query(point); -} - -std::vector> Scene::queryCollisions() const { - if (!spatialIndexingEnabled_) { - return {}; - } - return spatialManager_.queryCollisions(); -} - void Scene::collectRenderCommands(std::vector &commands, int parentZOrder) { if (!visible()) diff --git a/src/scene/shape_node.cpp b/src/scene/shape_node.cpp index 686c956..4f98395 100644 --- a/src/scene/shape_node.cpp +++ b/src/scene/shape_node.cpp @@ -112,17 +112,14 @@ Ptr ShapeNode::createFilledPolygon(const std::vector &points, void ShapeNode::setPoints(const std::vector &points) { points_ = points; - updateSpatialIndex(); } void ShapeNode::addPoint(const Vec2 &point) { points_.push_back(point); - updateSpatialIndex(); } void ShapeNode::clearPoints() { points_.clear(); - updateSpatialIndex(); } Rect ShapeNode::boundingBox() const { diff --git a/src/scene/sprite.cpp b/src/scene/sprite.cpp index b909b65..5e155bb 100644 --- a/src/scene/sprite.cpp +++ b/src/scene/sprite.cpp @@ -18,12 +18,10 @@ void Sprite::setTexture(Ptr texture) { textureRect_ = Rect(0, 0, static_cast(texture_->width()), static_cast(texture_->height())); } - updateSpatialIndex(); } void Sprite::setTextureRect(const Rect &rect) { textureRect_ = rect; - updateSpatialIndex(); } void Sprite::setColor(const Color &color) { color_ = color; } diff --git a/src/spatial/quadtree.cpp b/src/spatial/quadtree.cpp deleted file mode 100644 index 0cd394c..0000000 --- a/src/spatial/quadtree.cpp +++ /dev/null @@ -1,329 +0,0 @@ -#include -#include -#include -#include - -namespace extra2d { - -QuadTree::QuadTreeNode::QuadTreeNode(const Rect &bounds, int level) - : bounds(bounds), level(level) {} - -bool QuadTree::QuadTreeNode::contains(const Rect &rect) const { - return bounds.contains(rect); -} - -bool QuadTree::QuadTreeNode::intersects(const Rect &rect) const { - return bounds.intersects(rect); -} - -QuadTree::QuadTree(const Rect &worldBounds) : worldBounds_(worldBounds) { - root_ = std::make_unique(worldBounds, 0); -} - -void QuadTree::insert(Node *node, const Rect &bounds) { - if (!node || !root_->intersects(bounds)) - return; - insertIntoNode(root_.get(), node, bounds); - objectCount_++; -} - -void QuadTree::insertIntoNode(QuadTreeNode *node, Node *object, - const Rect &bounds) { - if (node->children[0]) { - int index = -1; - float midX = node->bounds.origin.x + node->bounds.size.width / 2.0f; - float midY = node->bounds.origin.y + node->bounds.size.height / 2.0f; - - bool top = bounds.origin.y + bounds.size.height <= midY; - bool bottom = bounds.origin.y >= midY; - bool left = bounds.origin.x + bounds.size.width <= midX; - bool right = bounds.origin.x >= midX; - - if (top && left) - index = 0; - else if (top && right) - index = 1; - else if (bottom && left) - index = 2; - else if (bottom && right) - index = 3; - - if (index != -1) { - insertIntoNode(node->children[index].get(), object, bounds); - return; - } - } - - node->objects.emplace_back(object, bounds); - - if (node->objects.size() > MAX_OBJECTS && node->level < MAX_LEVELS) { - if (!node->children[0]) { - split(node); - } - } -} - -void QuadTree::split(QuadTreeNode *node) { - float midX = node->bounds.origin.x + node->bounds.size.width / 2.0f; - float midY = node->bounds.origin.y + node->bounds.size.height / 2.0f; - - node->children[0] = std::make_unique( - Rect(node->bounds.origin.x, node->bounds.origin.y, - node->bounds.size.width / 2.0f, node->bounds.size.height / 2.0f), - node->level + 1); - node->children[1] = std::make_unique( - Rect(midX, node->bounds.origin.y, node->bounds.size.width / 2.0f, - node->bounds.size.height / 2.0f), - node->level + 1); - node->children[2] = std::make_unique( - Rect(node->bounds.origin.x, midY, node->bounds.size.width / 2.0f, - node->bounds.size.height / 2.0f), - node->level + 1); - node->children[3] = std::make_unique( - Rect(midX, midY, node->bounds.size.width / 2.0f, - node->bounds.size.height / 2.0f), - node->level + 1); - - auto objects = std::move(node->objects); - node->objects.clear(); - - for (const auto &[obj, bounds] : objects) { - insertIntoNode(node, obj, bounds); - } -} - -void QuadTree::remove(Node *node) { - if (!node) - return; - if (removeFromNode(root_.get(), node)) { - objectCount_--; - } -} - -bool QuadTree::removeFromNode(QuadTreeNode *node, Node *object) { - auto it = - std::find_if(node->objects.begin(), node->objects.end(), - [object](const auto &pair) { return pair.first == object; }); - - if (it != node->objects.end()) { - node->objects.erase(it); - return true; - } - - if (node->children[0]) { - for (auto &child : node->children) { - if (removeFromNode(child.get(), object)) { - return true; - } - } - } - - return false; -} - -void QuadTree::update(Node *node, const Rect &newBounds) { - remove(node); - insert(node, newBounds); -} - -std::vector QuadTree::query(const Rect &area) const { - std::vector results; - queryNode(root_.get(), area, results); - return results; -} - -void QuadTree::queryNode(const QuadTreeNode *node, const Rect &area, - std::vector &results) const { - if (!node || !node->intersects(area)) - return; - - for (const auto &[obj, bounds] : node->objects) { - if (bounds.intersects(area)) { - results.push_back(obj); - } - } - - if (node->children[0]) { - for (const auto &child : node->children) { - queryNode(child.get(), area, results); - } - } -} - -std::vector QuadTree::query(const Vec2 &point) const { - std::vector results; - queryNode(root_.get(), point, results); - return results; -} - -void QuadTree::queryNode(const QuadTreeNode *node, const Vec2 &point, - std::vector &results) const { - if (!node || !node->bounds.containsPoint(point)) - return; - - for (const auto &[obj, bounds] : node->objects) { - if (bounds.containsPoint(point)) { - results.push_back(obj); - } - } - - if (node->children[0]) { - for (const auto &child : node->children) { - queryNode(child.get(), point, results); - } - } -} - -std::vector> QuadTree::queryCollisions() const { - std::vector> collisions; - collectCollisions(root_.get(), collisions); - return collisions; -} - -void QuadTree::detectCollisionsInNode( - const std::vector> &objects, - std::vector> &collisions) const { - size_t n = objects.size(); - if (n < 2) - return; - - // 使用扫描线算法优化碰撞检测 - // 按 x 坐标排序,只检查可能重叠的对象 - collisionBuffer_.clear(); - collisionBuffer_.reserve(n); - collisionBuffer_.assign(objects.begin(), objects.end()); - - // 按左边界排序 - std::sort(collisionBuffer_.begin(), collisionBuffer_.end(), - [](const auto &a, const auto &b) { - return a.second.origin.x < b.second.origin.x; - }); - - // 扫描线检测 - for (size_t i = 0; i < n; ++i) { - const auto &[objA, boundsA] = collisionBuffer_[i]; - float rightA = boundsA.origin.x + boundsA.size.width; - - // 只检查右边界在 objA 右侧的对象 - for (size_t j = i + 1; j < n; ++j) { - const auto &[objB, boundsB] = collisionBuffer_[j]; - - // 如果 objB 的左边界超过 objA 的右边界,后续对象都不会碰撞 - if (boundsB.origin.x > rightA) - break; - - // 快速 AABB 检测 - if (boundsA.intersects(boundsB)) { - collisions.emplace_back(objA, objB); - } - } - } -} - -void QuadTree::collectCollisions( - const QuadTreeNode *node, - std::vector> &collisions) const { - if (!node) - return; - - // 使用迭代而非递归,避免深层树栈溢出 - struct StackItem { - const QuadTreeNode *node; - size_t ancestorStart; - size_t ancestorEnd; - }; - - std::vector stack; - stack.reserve(32); - stack.push_back({node, 0, 0}); - - // 祖先对象列表,用于检测跨节点碰撞 - collisionBuffer_.clear(); - - while (!stack.empty()) { - StackItem item = stack.back(); - stack.pop_back(); - - const QuadTreeNode *current = item.node; - if (!current) - continue; - - // 确保 ancestorEnd 不超过当前 buffer 大小 - size_t validAncestorEnd = std::min(item.ancestorEnd, collisionBuffer_.size()); - - // 检测当前节点对象与祖先对象的碰撞 - for (const auto &[obj, bounds] : current->objects) { - for (size_t i = item.ancestorStart; i < validAncestorEnd; ++i) { - const auto &[ancestorObj, ancestorBounds] = collisionBuffer_[i]; - if (bounds.intersects(ancestorBounds)) { - collisions.emplace_back(ancestorObj, obj); - } - } - } - - // 检测当前节点内对象之间的碰撞(使用扫描线算法) - detectCollisionsInNode(current->objects, collisions); - - // 记录当前节点的对象作为祖先 - size_t oldSize = collisionBuffer_.size(); - collisionBuffer_.insert(collisionBuffer_.end(), current->objects.begin(), - current->objects.end()); - - // 将子节点压入栈(逆序以保持遍历顺序) - if (current->children[0]) { - for (int i = 3; i >= 0; --i) { - if (current->children[i]) { - stack.push_back({current->children[i].get(), oldSize, - collisionBuffer_.size()}); - } - } - } - - // 恢复祖先列表(模拟递归返回) - // 只有当栈顶元素的祖先范围与当前不同时,才需要恢复 - if (stack.empty()) { - collisionBuffer_.resize(oldSize); - } else { - const auto& nextItem = stack.back(); - // 如果下一个节点的祖先范围与当前不同,则需要恢复到其祖先起始位置 - if (nextItem.ancestorStart != oldSize) { - collisionBuffer_.resize(oldSize); - } - } - } -} - -void QuadTree::clear() { - root_ = std::make_unique(worldBounds_, 0); - objectCount_ = 0; -} - -size_t QuadTree::size() const { return objectCount_; } - -bool QuadTree::empty() const { return objectCount_ == 0; } - -void QuadTree::rebuild() { - std::vector> allObjects; - - std::function collect = [&](QuadTreeNode *node) { - if (!node) - return; - for (const auto &obj : node->objects) { - allObjects.push_back(obj); - } - if (node->children[0]) { - for (auto &child : node->children) { - collect(child.get()); - } - } - }; - - collect(root_.get()); - clear(); - - for (const auto &[obj, bounds] : allObjects) { - insert(obj, bounds); - } -} - -} // namespace extra2d diff --git a/src/spatial/spatial_hash.cpp b/src/spatial/spatial_hash.cpp deleted file mode 100644 index d904201..0000000 --- a/src/spatial/spatial_hash.cpp +++ /dev/null @@ -1,256 +0,0 @@ -#include -#include -#include -#include - -namespace extra2d { - -// Cell 实现 -void SpatialHash::Cell::insert(Node *node) { - // 检查是否已存在 - if (!contains(node)) { - objects.push_back(node); - } -} - -void SpatialHash::Cell::remove(Node *node) { - auto it = std::find(objects.begin(), objects.end(), node); - if (it != objects.end()) { - // 用最后一个元素替换,然后pop_back,O(1)操作 - *it = objects.back(); - objects.pop_back(); - } -} - -bool SpatialHash::Cell::contains(Node *node) const { - return std::find(objects.begin(), objects.end(), node) != objects.end(); -} - -SpatialHash::SpatialHash(float cellSize) : cellSize_(cellSize) { - // 预分配查询缓冲区,避免重复分配 - queryBuffer_.reserve(64); - collisionBuffer_.reserve(128); -} - -SpatialHash::CellKey SpatialHash::getCellKey(float x, float y) const { - int64_t cellX = static_cast(std::floor(x / cellSize_)); - int64_t cellY = static_cast(std::floor(y / cellSize_)); - return {cellX, cellY}; -} - -void SpatialHash::getCellsForRect(const Rect &rect, - std::vector &cells) const { - cells.clear(); - - CellKey minCell = getCellKey(rect.origin.x, rect.origin.y); - CellKey maxCell = getCellKey(rect.origin.x + rect.size.width, - rect.origin.y + rect.size.height); - - // 预分配空间 - size_t cellCount = (maxCell.first - minCell.first + 1) * - (maxCell.second - minCell.second + 1); - cells.reserve(cellCount); - - for (int64_t x = minCell.first; x <= maxCell.first; ++x) { - for (int64_t y = minCell.second; y <= maxCell.second; ++y) { - cells.emplace_back(x, y); - } - } -} - -void SpatialHash::insertIntoCells(Node *node, const Rect &bounds) { - std::vector cells; - getCellsForRect(bounds, cells); - - for (const auto &cell : cells) { - grid_[cell].insert(node); - } -} - -void SpatialHash::removeFromCells(Node *node, const Rect &bounds) { - std::vector cells; - getCellsForRect(bounds, cells); - - for (const auto &cell : cells) { - auto it = grid_.find(cell); - if (it != grid_.end()) { - it->second.remove(node); - if (it->second.empty()) { - grid_.erase(it); - } - } - } -} - -void SpatialHash::insert(Node *node, const Rect &bounds) { - if (!node) - return; - - // 检查节点是否已存在,如果存在则先移除 - auto it = objectBounds_.find(node); - if (it != objectBounds_.end()) { - removeFromCells(node, it->second); - it->second = bounds; - } else { - objectBounds_[node] = bounds; - objectCount_++; - } - - insertIntoCells(node, bounds); -} - -void SpatialHash::remove(Node *node) { - if (!node) - return; - - auto it = objectBounds_.find(node); - if (it != objectBounds_.end()) { - removeFromCells(node, it->second); - objectBounds_.erase(it); - objectCount_--; - } else { - // 节点不在 objectBounds_ 中,但可能还在 grid_ 的某些单元格中 - // 需要遍历所有单元格来移除 - for (auto &[cellKey, cell] : grid_) { - cell.remove(node); - } - } -} - -void SpatialHash::update(Node *node, const Rect &newBounds) { - auto it = objectBounds_.find(node); - if (it != objectBounds_.end()) { - removeFromCells(node, it->second); - insertIntoCells(node, newBounds); - it->second = newBounds; - } -} - -std::vector SpatialHash::query(const Rect &area) const { - queryBuffer_.clear(); - - std::vector cells; - getCellsForRect(area, cells); - - // 使用排序+去重代替unordered_set,减少内存分配 - for (const auto &cell : cells) { - auto it = grid_.find(cell); - if (it != grid_.end()) { - for (Node *node : it->second.objects) { - queryBuffer_.push_back(node); - } - } - } - - // 排序并去重 - std::sort(queryBuffer_.begin(), queryBuffer_.end()); - queryBuffer_.erase( - std::unique(queryBuffer_.begin(), queryBuffer_.end()), - queryBuffer_.end()); - - // 过滤实际相交的对象 - std::vector results; - results.reserve(queryBuffer_.size()); - - for (Node *node : queryBuffer_) { - auto boundsIt = objectBounds_.find(node); - if (boundsIt != objectBounds_.end() && - boundsIt->second.intersects(area)) { - results.push_back(node); - } - } - - return results; -} - -std::vector SpatialHash::query(const Vec2 &point) const { - std::vector results; - - CellKey cell = getCellKey(point.x, point.y); - auto it = grid_.find(cell); - - if (it != grid_.end()) { - for (Node *node : it->second.objects) { - auto boundsIt = objectBounds_.find(node); - if (boundsIt != objectBounds_.end() && - boundsIt->second.containsPoint(point)) { - results.push_back(node); - } - } - } - - return results; -} - -std::vector> SpatialHash::queryCollisions() const { - collisionBuffer_.clear(); - - // 使用排序+唯一性检查代替unordered_set - std::vector> tempCollisions; - tempCollisions.reserve(objectCount_ * 2); - - for (const auto &[cell, cellData] : grid_) { - const auto &objects = cellData.objects; - size_t count = objects.size(); - - // 使用扫描线算法优化单元格内碰撞检测 - for (size_t i = 0; i < count; ++i) { - Node *nodeA = objects[i]; - auto boundsA = objectBounds_.find(nodeA); - if (boundsA == objectBounds_.end()) - continue; - - for (size_t j = i + 1; j < count; ++j) { - Node *nodeB = objects[j]; - auto boundsB = objectBounds_.find(nodeB); - if (boundsB == objectBounds_.end()) - continue; - - if (boundsA->second.intersects(boundsB->second)) { - // 确保有序对,便于去重 - if (nodeA < nodeB) { - tempCollisions.emplace_back(nodeA, nodeB); - } else { - tempCollisions.emplace_back(nodeB, nodeA); - } - } - } - } - } - - // 排序并去重 - std::sort(tempCollisions.begin(), tempCollisions.end()); - tempCollisions.erase( - std::unique(tempCollisions.begin(), tempCollisions.end()), - tempCollisions.end()); - - return tempCollisions; -} - -void SpatialHash::clear() { - grid_.clear(); - objectBounds_.clear(); - objectCount_ = 0; -} - -size_t SpatialHash::size() const { return objectCount_; } - -bool SpatialHash::empty() const { return objectCount_ == 0; } - -void SpatialHash::rebuild() { - auto bounds = objectBounds_; - clear(); - - for (const auto &[node, bound] : bounds) { - insert(node, bound); - } -} - -void SpatialHash::setCellSize(float cellSize) { - if (cellSize != cellSize_ && cellSize > 0) { - cellSize_ = cellSize; - rebuild(); - } -} - -} // namespace extra2d diff --git a/src/spatial/spatial_manager.cpp b/src/spatial/spatial_manager.cpp deleted file mode 100644 index 861ac15..0000000 --- a/src/spatial/spatial_manager.cpp +++ /dev/null @@ -1,198 +0,0 @@ -#include -#include -#include -#include -#include - -namespace extra2d { - -SpatialManager::SpatialManager() : worldBounds_(0, 0, 10000, 10000) { - selectOptimalStrategy(); -} - -SpatialManager::SpatialManager(const Rect &worldBounds) - : worldBounds_(worldBounds) { - selectOptimalStrategy(); -} - -void SpatialManager::setStrategy(SpatialStrategy strategy) { - if (currentStrategy_ == strategy) - return; - - currentStrategy_ = strategy; - rebuild(); -} - -void SpatialManager::setAutoThresholds(size_t quadTreeThreshold, - size_t hashThreshold) { - quadTreeThreshold_ = quadTreeThreshold; - hashThreshold_ = hashThreshold; - - if (currentStrategy_ == SpatialStrategy::Auto) { - selectOptimalStrategy(); - } -} - -void SpatialManager::setWorldBounds(const Rect &bounds) { - worldBounds_ = bounds; - rebuild(); -} - -void SpatialManager::insert(Node *node, const Rect &bounds) { - if (!index_) { - selectOptimalStrategy(); - } - - if (index_) { - index_->insert(node, bounds); - } -} - -void SpatialManager::remove(Node *node) { - if (index_) { - index_->remove(node); - } -} - -void SpatialManager::update(Node *node, const Rect &newBounds) { - if (index_) { - index_->update(node, newBounds); - } -} - -std::vector SpatialManager::query(const Rect &area) const { - if (!index_) - return {}; - - auto start = std::chrono::high_resolution_clock::now(); - auto results = index_->query(area); - auto end = std::chrono::high_resolution_clock::now(); - - queryCount_++; - totalQueryTime_ += - std::chrono::duration_cast(end - start) - .count(); - - return results; -} - -std::vector SpatialManager::query(const Vec2 &point) const { - if (!index_) - return {}; - return index_->query(point); -} - -std::vector> SpatialManager::queryCollisions() const { - if (!index_) - return {}; - return index_->queryCollisions(); -} - -void SpatialManager::query(const Rect &area, - const QueryCallback &callback) const { - auto results = query(area); - for (Node *node : results) { - if (!callback(node)) - break; - } -} - -void SpatialManager::query(const Vec2 &point, - const QueryCallback &callback) const { - auto results = query(point); - for (Node *node : results) { - if (!callback(node)) - break; - } -} - -void SpatialManager::clear() { - if (index_) { - index_->clear(); - } -} - -size_t SpatialManager::size() const { return index_ ? index_->size() : 0; } - -bool SpatialManager::empty() const { return index_ ? index_->empty() : true; } - -void SpatialManager::rebuild() { - if (!index_) { - selectOptimalStrategy(); - return; - } - - auto oldIndex = std::move(index_); - selectOptimalStrategy(); - - if (index_ && oldIndex) { - auto bounds = Rect(worldBounds_); - auto objects = oldIndex->query(bounds); - for (Node *node : objects) { - if (node) { - auto nodeBounds = node->boundingBox(); - index_->insert(node, nodeBounds); - } - } - } -} - -void SpatialManager::optimize() { - if (currentStrategy_ == SpatialStrategy::Auto) { - selectOptimalStrategy(); - } - - if (index_) { - index_->rebuild(); - } -} - -SpatialStrategy SpatialManager::getCurrentStrategy() const { - return activeStrategy_; -} - -const char *SpatialManager::getStrategyName() const { - switch (activeStrategy_) { - case SpatialStrategy::QuadTree: - return "QuadTree"; - case SpatialStrategy::SpatialHash: - return "SpatialHash"; - default: - return "Unknown"; - } -} - -std::unique_ptr -SpatialManager::createIndex(SpatialStrategy strategy, const Rect &bounds) { - switch (strategy) { - case SpatialStrategy::QuadTree: - return std::make_unique(bounds); - case SpatialStrategy::SpatialHash: - return std::make_unique(64.0f); - default: - return std::make_unique(bounds); - } -} - -void SpatialManager::selectOptimalStrategy() { - if (currentStrategy_ != SpatialStrategy::Auto) { - activeStrategy_ = currentStrategy_; - } else { - size_t currentSize = index_ ? index_->size() : 0; - - if (currentSize < quadTreeThreshold_) { - activeStrategy_ = SpatialStrategy::QuadTree; - } else if (currentSize > hashThreshold_) { - activeStrategy_ = SpatialStrategy::SpatialHash; - } else { - // Keep current strategy in transition zone - if (!index_) { - activeStrategy_ = SpatialStrategy::QuadTree; - } - } - } - - index_ = createIndex(activeStrategy_, worldBounds_); -} - -} // namespace extra2d diff --git a/src/ui/button.cpp b/src/ui/button.cpp index ca9a535..d93617b 100644 --- a/src/ui/button.cpp +++ b/src/ui/button.cpp @@ -16,7 +16,6 @@ namespace extra2d { */ Button::Button() { setAnchor(0.0f, 0.0f); - setSpatialIndexed(false); auto &dispatcher = getEventDispatcher(); dispatcher.addListener(EventType::UIHoverEnter, [this](Event &) { diff --git a/src/ui/label.cpp b/src/ui/label.cpp index 62a76a2..951ac2b 100644 --- a/src/ui/label.cpp +++ b/src/ui/label.cpp @@ -52,7 +52,6 @@ Ptr