feat(场景系统): 重构场景过渡系统并添加视口适配功能

重构过渡系统为基于 TransitionScene 的场景中介模式,新增多种过渡效果
为 BaseScene 添加视口适配功能,支持居中显示游戏内容
将按钮组件扩展支持切换模式,优化 UI 系统文档
更新示例项目使用新过渡系统和视口适配

新增 TransitionScene 基类及多种过渡效果实现:
- TransitionFadeScene 淡入淡出过渡
- TransitionSlideScene 滑动过渡
- TransitionScaleScene 缩放过渡
- TransitionFlipScene 翻页过渡
- TransitionBoxScene 方块过渡

BaseScene 提供视口适配功能:
- 计算居中视口参数
- 处理窗口大小变化
- 支持作为 TransitionScene 子场景渲染

UI 系统改进:
- Button 组件新增切换模式支持
- 合并 ToggleImageButton 功能到 Button
- 更新文档和示例使用切换按钮

示例项目更新:
- flappy_bird 使用新过渡系统
- push_box 实现完整场景结构和视口适配
- 更新场景切换 API 使用方式
This commit is contained in:
ChestnutYueyue 2026-02-13 13:56:18 +08:00
parent 4f641a2854
commit b78c493590
35 changed files with 2275 additions and 1481 deletions

View File

@ -90,7 +90,7 @@ public:
// 便捷方法 // 便捷方法
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void enterScene(Ptr<class Scene> scene); void enterScene(Ptr<class Scene> scene);
void enterScene(Ptr<class Scene> scene, Ptr<class Transition> transition); void enterScene(Ptr<class Scene> scene, Ptr<class TransitionScene> transitionScene);
float deltaTime() const { return deltaTime_; } float deltaTime() const { return deltaTime_; }
float totalTime() const { return totalTime_; } float totalTime() const { return totalTime_; }

View File

@ -29,7 +29,12 @@
#include <extra2d/scene/sprite.h> #include <extra2d/scene/sprite.h>
#include <extra2d/scene/shape_node.h> #include <extra2d/scene/shape_node.h>
#include <extra2d/scene/scene_manager.h> #include <extra2d/scene/scene_manager.h>
#include <extra2d/scene/transition.h> #include <extra2d/scene/transition_scene.h>
#include <extra2d/scene/transition_fade_scene.h>
#include <extra2d/scene/transition_slide_scene.h>
#include <extra2d/scene/transition_scale_scene.h>
#include <extra2d/scene/transition_flip_scene.h>
#include <extra2d/scene/transition_box_scene.h>
// Animation // Animation
#include <extra2d/animation/sprite_frame.h> #include <extra2d/animation/sprite_frame.h>

View File

@ -56,7 +56,7 @@ public:
// 渲染和更新 // 渲染和更新
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void renderScene(RenderBackend &renderer); void renderScene(RenderBackend &renderer);
void renderContent(RenderBackend &renderer); virtual void renderContent(RenderBackend &renderer);
void updateScene(float dt); void updateScene(float dt);
void collectRenderCommands(std::vector<RenderCommand> &commands, void collectRenderCommands(std::vector<RenderCommand> &commands,
int parentZOrder = 0) override; int parentZOrder = 0) override;
@ -92,7 +92,12 @@ protected:
void onEnter() override; void onEnter() override;
void onExit() override; void onExit() override;
// 过渡场景生命周期回调(供 TransitionScene 使用)
virtual void onExitTransitionDidStart() {}
virtual void onEnterTransitionDidFinish() {}
friend class SceneManager; friend class SceneManager;
friend class TransitionScene;
private: private:
Color backgroundColor_ = Colors::Black; Color backgroundColor_ = Colors::Black;

View File

@ -2,6 +2,7 @@
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <extra2d/scene/scene.h> #include <extra2d/scene/scene.h>
#include <extra2d/scene/transition_scene.h>
#include <functional> #include <functional>
#include <stack> #include <stack>
#include <string> #include <string>
@ -12,21 +13,7 @@ namespace extra2d {
// 前向声明 // 前向声明
struct RenderCommand; struct RenderCommand;
class Transition; class TransitionScene;
// ============================================================================
// 场景切换特效类型
// ============================================================================
enum class TransitionType {
None,
Fade,
SlideLeft,
SlideRight,
SlideUp,
SlideDown,
Scale,
Flip
};
// ============================================================================ // ============================================================================
// 场景管理器 - 管理场景的生命周期和切换 // 场景管理器 - 管理场景的生命周期和切换
@ -116,27 +103,27 @@ public:
// 场景切换(供 Application 使用) // 场景切换(供 Application 使用)
void enterScene(Ptr<Scene> scene); void enterScene(Ptr<Scene> scene);
void enterScene(Ptr<Scene> scene, Ptr<class Transition> transition); void enterScene(Ptr<Scene> scene, Ptr<TransitionScene> transitionScene);
private: private:
void doSceneSwitch(); void doSceneSwitch();
void startTransition(Ptr<Scene> from, Ptr<Scene> to, TransitionType type, void startTransition(Ptr<Scene> from, Ptr<Scene> to, TransitionType type,
float duration, Function<void()> stackAction); float duration, Function<void()> stackAction);
void updateTransition(float dt);
void finishTransition(); void finishTransition();
void dispatchPointerEvents(Scene &scene); void dispatchPointerEvents(Scene &scene);
// 创建过渡场景
Ptr<TransitionScene> createTransitionScene(TransitionType type,
float duration,
Ptr<Scene> inScene);
std::stack<Ptr<Scene>> sceneStack_; std::stack<Ptr<Scene>> sceneStack_;
std::unordered_map<std::string, Ptr<Scene>> namedScenes_; std::unordered_map<std::string, Ptr<Scene>> namedScenes_;
// Transition state // Transition state
bool isTransitioning_ = false; bool isTransitioning_ = false;
TransitionType currentTransition_ = TransitionType::None; TransitionType currentTransition_ = TransitionType::None;
float transitionDuration_ = 0.0f; Ptr<TransitionScene> activeTransitionScene_;
float transitionElapsed_ = 0.0f;
Ptr<Scene> outgoingScene_;
Ptr<Scene> incomingScene_;
Ptr<Transition> activeTransition_;
Function<void()> transitionStackAction_; Function<void()> transitionStackAction_;
TransitionCallback transitionCallback_; TransitionCallback transitionCallback_;

View File

@ -1,137 +0,0 @@
#pragma once
#include <extra2d/core/color.h>
#include <extra2d/core/types.h>
#include <extra2d/scene/scene.h>
#include <functional>
namespace extra2d {
// ============================================================================
// 过渡方向
// ============================================================================
enum class TransitionDirection { Left, Right, Up, Down };
// ============================================================================
// 过渡效果基类
// ============================================================================
class Transition : public std::enable_shared_from_this<Transition> {
public:
using FinishCallback = std::function<void()>;
Transition(float duration);
virtual ~Transition() = default;
// 开始过渡
void start(Ptr<Scene> from, Ptr<Scene> to);
// 更新过渡进度
void update(float dt);
// 渲染过渡效果
virtual void render(RenderBackend &renderer);
// 是否完成
bool isFinished() const { return isFinished_; }
// 获取进度 [0, 1]
float getProgress() const { return progress_; }
// 获取淡入淡出进度 (0->1 for fade in, 1->0 for fade out)
float getFadeInAlpha() const;
float getFadeOutAlpha() const;
// 设置完成回调
void setFinishCallback(FinishCallback callback) {
finishCallback_ = callback;
}
// 获取源场景和目标场景
Ptr<Scene> getOutgoingScene() const { return outgoingScene_; }
Ptr<Scene> getIncomingScene() const { return incomingScene_; }
protected:
// 子类实现具体的渲染效果
virtual void onRenderTransition(RenderBackend &renderer, float progress) = 0;
// 过渡完成时调用
virtual void onFinish();
float duration_;
float elapsed_;
float progress_;
bool isFinished_;
bool isStarted_;
Ptr<Scene> outgoingScene_;
Ptr<Scene> incomingScene_;
FinishCallback finishCallback_;
};
// ============================================================================
// 淡入淡出过渡
// ============================================================================
class FadeTransition : public Transition {
public:
FadeTransition(float duration);
protected:
void onRenderTransition(RenderBackend &renderer, float progress) override;
};
// ============================================================================
// 滑动过渡
// ============================================================================
class SlideTransition : public Transition {
public:
SlideTransition(float duration, TransitionDirection direction);
protected:
void onRenderTransition(RenderBackend &renderer, float progress) override;
private:
TransitionDirection direction_;
};
// ============================================================================
// 缩放过渡
// ============================================================================
class ScaleTransition : public Transition {
public:
ScaleTransition(float duration);
protected:
void onRenderTransition(RenderBackend &renderer, float progress) override;
};
// ============================================================================
// 翻页过渡
// ============================================================================
class FlipTransition : public Transition {
public:
enum class Axis { Horizontal, Vertical };
FlipTransition(float duration, Axis axis = Axis::Horizontal);
protected:
void onRenderTransition(RenderBackend &renderer, float progress) override;
private:
Axis axis_;
};
// ============================================================================
// 马赛克/方块过渡
// ============================================================================
class BoxTransition : public Transition {
public:
BoxTransition(float duration, int divisions = 8);
protected:
void onRenderTransition(RenderBackend &renderer, float progress) override;
private:
int divisions_;
};
} // namespace extra2d

View File

@ -0,0 +1,34 @@
#pragma once
#include <extra2d/scene/transition_scene.h>
namespace extra2d {
// ============================================================================
// 方块/马赛克过渡场景
// 实现原理:
// 1. 将屏幕分成多个方块
// 2. 方块逐个消失,显示新场景
// ============================================================================
class TransitionBoxScene : public TransitionScene {
public:
/**
* @brief
* @param duration
* @param inScene
* @param divisions 8 8x8
*/
TransitionBoxScene(float duration, Ptr<Scene> inScene, int divisions = 8);
static Ptr<TransitionBoxScene> create(float duration, Ptr<Scene> inScene,
int divisions = 8);
protected:
void onTransitionStart() override;
void renderContent(RenderBackend &renderer) override;
private:
int divisions_;
};
} // namespace extra2d

View File

@ -0,0 +1,54 @@
#pragma once
#include <extra2d/scene/transition_scene.h>
#include <extra2d/scene/sprite.h>
#include <extra2d/core/color.h>
namespace extra2d {
// ============================================================================
// 淡入淡出过渡场景
// 实现原理:
// 1. 创建一个纯色精灵作为遮罩层
// 2. 第一阶段:遮罩从透明淡入到不透明(黑屏),同时显示旧场景
// 3. 切换显示新场景
// 4. 第二阶段:遮罩从不透明淡出到透明,显示新场景
// ============================================================================
class TransitionFadeScene : public TransitionScene {
public:
/**
* @brief
* @param duration
* @param inScene
* @param color
*/
TransitionFadeScene(float duration, Ptr<Scene> inScene,
const Color &color = Colors::Black);
static Ptr<TransitionFadeScene> create(float duration, Ptr<Scene> inScene,
const Color &color = Colors::Black);
protected:
/**
* @brief
*
*/
void onTransitionStart() override;
/**
* @brief
*
*/
void renderContent(RenderBackend &renderer) override;
private:
/**
* @brief 退
*/
void hideOutShowIn();
Color maskColor_; // 遮罩颜色
bool hasSwitched_ = false; // 是否已经切换场景
};
} // namespace extra2d

View File

@ -0,0 +1,37 @@
#pragma once
#include <extra2d/scene/transition_scene.h>
namespace extra2d {
// ============================================================================
// 翻页过渡场景
// 实现原理:
// 1. 前半段:旧场景翻转消失
// 2. 后半段:新场景翻转出现
// ============================================================================
class TransitionFlipScene : public TransitionScene {
public:
enum class Axis { Horizontal, Vertical };
/**
* @brief
* @param duration
* @param inScene
* @param axis
*/
TransitionFlipScene(float duration, Ptr<Scene> inScene,
Axis axis = Axis::Horizontal);
static Ptr<TransitionFlipScene> create(float duration, Ptr<Scene> inScene,
Axis axis = Axis::Horizontal);
protected:
void onTransitionStart() override;
void renderContent(RenderBackend &renderer) override;
private:
Axis axis_;
};
} // namespace extra2d

View File

@ -0,0 +1,29 @@
#pragma once
#include <extra2d/scene/transition_scene.h>
namespace extra2d {
// ============================================================================
// 缩放过渡场景
// 实现原理:
// 1. 旧场景缩小消失
// 2. 新场景放大出现
// ============================================================================
class TransitionScaleScene : public TransitionScene {
public:
/**
* @brief
* @param duration
* @param inScene
*/
TransitionScaleScene(float duration, Ptr<Scene> inScene);
static Ptr<TransitionScaleScene> create(float duration, Ptr<Scene> inScene);
protected:
void onTransitionStart() override;
void renderContent(RenderBackend &renderer) override;
};
} // namespace extra2d

View File

@ -0,0 +1,127 @@
#pragma once
#include <extra2d/scene/scene.h>
#include <functional>
namespace extra2d {
// ============================================================================
// 过渡方向
// ============================================================================
enum class TransitionDirection { Left, Right, Up, Down };
// ============================================================================
// 过渡效果类型
// ============================================================================
enum class TransitionType {
None,
Fade,
SlideLeft,
SlideRight,
SlideUp,
SlideDown,
Scale,
Flip,
Box
};
// ============================================================================
// 过渡场景基类 - 继承自 Scene作为中介场景管理过渡效果
// 设计参考 Cocos2d-x 的 TransitionScene
// ============================================================================
class TransitionScene : public Scene {
public:
using FinishCallback = std::function<void()>;
/**
* @brief
* @param duration
* @param inScene
*/
TransitionScene(float duration, Ptr<Scene> inScene);
~TransitionScene() override = default;
// ------------------------------------------------------------------------
// 场景管理
// ------------------------------------------------------------------------
/**
* @brief
*/
Ptr<Scene> getInScene() const { return inScene_; }
/**
* @brief 退
*/
Ptr<Scene> getOutScene() const { return outScene_; }
/**
* @brief 退 SceneManager
*/
void setOutScene(Ptr<Scene> outScene) { outScene_ = outScene; }
/**
* @brief
*/
void setFinishCallback(FinishCallback callback) { finishCallback_ = callback; }
/**
* @brief
*/
float getDuration() const { return duration_; }
/**
* @brief [0, 1]
*/
float getProgress() const { return progress_; }
/**
* @brief
*/
bool isFinished() const { return isFinished_; }
/**
* @brief SceneManager
*/
void finish();
// ------------------------------------------------------------------------
// 渲染 - 在 TransitionScene 上渲染新旧两个子场景
// ------------------------------------------------------------------------
void renderContent(RenderBackend &renderer) override;
// ------------------------------------------------------------------------
// 生命周期
// ------------------------------------------------------------------------
void onEnter() override;
void onExit() override;
protected:
/**
* @brief
* onEnter finish()
*/
virtual void onTransitionStart() = 0;
/**
* @brief
*/
virtual void drawOutScene(RenderBackend &renderer);
/**
* @brief
*/
virtual void drawInScene(RenderBackend &renderer);
float duration_;
float elapsed_ = 0.0f;
float progress_ = 0.0f;
bool isFinished_ = false;
Ptr<Scene> inScene_; // 要进入的场景
Ptr<Scene> outScene_; // 要退出的场景
FinishCallback finishCallback_;
};
} // namespace extra2d

View File

@ -0,0 +1,35 @@
#pragma once
#include <extra2d/scene/transition_scene.h>
namespace extra2d {
// ============================================================================
// 滑动过渡场景
// 实现原理:
// 1. 旧场景向指定方向滑出
// 2. 新场景从相反方向滑入
// ============================================================================
class TransitionSlideScene : public TransitionScene {
public:
/**
* @brief
* @param duration
* @param inScene
* @param direction
*/
TransitionSlideScene(float duration, Ptr<Scene> inScene,
TransitionDirection direction);
static Ptr<TransitionSlideScene> create(float duration, Ptr<Scene> inScene,
TransitionDirection direction);
protected:
void onTransitionStart() override;
void renderContent(RenderBackend &renderer) override;
private:
TransitionDirection direction_;
};
} // namespace extra2d

View File

@ -95,6 +95,8 @@ public:
// 边框设置 // 边框设置
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setBorder(const Color &color, float width); void setBorder(const Color &color, float width);
float getBorderWidth() const { return borderWidth_; }
Color getBorderColor() const { return borderColor_; }
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 图片背景设置 // 图片背景设置
@ -102,6 +104,22 @@ public:
void setBackgroundImage(Ptr<Texture> normal, Ptr<Texture> hover = nullptr, void setBackgroundImage(Ptr<Texture> normal, Ptr<Texture> hover = nullptr,
Ptr<Texture> pressed = nullptr); Ptr<Texture> pressed = nullptr);
void setBackgroundImage(Ptr<Texture> texture, const Rect &rect); void setBackgroundImage(Ptr<Texture> texture, const Rect &rect);
/**
* @brief
* @param offNormal
* @param onNormal
* @param offHover
* @param onHover
* @param offPressed
* @param onPressed
*/
void setStateBackgroundImage(Ptr<Texture> offNormal, Ptr<Texture> onNormal,
Ptr<Texture> offHover = nullptr,
Ptr<Texture> onHover = nullptr,
Ptr<Texture> offPressed = nullptr,
Ptr<Texture> onPressed = nullptr);
void setBackgroundImageScaleMode(ImageScaleMode mode); void setBackgroundImageScaleMode(ImageScaleMode mode);
void setCustomSize(const Vec2 &size); void setCustomSize(const Vec2 &size);
void setCustomSize(float width, float height); void setCustomSize(float width, float height);
@ -131,6 +149,63 @@ public:
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
void setOnClick(Function<void()> callback); void setOnClick(Function<void()> callback);
// ------------------------------------------------------------------------
// 切换模式支持Toggle Button
// ------------------------------------------------------------------------
/**
* @brief
* @param enabled true on/off
*/
void setToggleMode(bool enabled);
/**
* @brief
* @return true
*/
bool isToggleMode() const { return toggleMode_; }
/**
* @brief
* @param on true false
*/
void setOn(bool on);
/**
* @brief
* @return true false
*/
bool isOn() const { return isOn_; }
/**
* @brief
*/
void toggle();
/**
* @brief
* @param callback
*/
void setOnStateChange(Function<void(bool)> callback);
// ------------------------------------------------------------------------
// 状态文字设置(用于切换按钮)
// ------------------------------------------------------------------------
/**
* @brief
* @param textOff
* @param textOn
*/
void setStateText(const std::string &textOff, const std::string &textOn);
/**
* @brief
* @param colorOff
* @param colorOn
*/
void setStateTextColor(const Color &colorOff, const Color &colorOn);
Rect getBoundingBox() const override; Rect getBoundingBox() const override;
protected: protected:
@ -170,6 +245,12 @@ private:
bool useImageBackground_ = false; bool useImageBackground_ = false;
bool useTextureRect_ = false; bool useTextureRect_ = false;
// 切换按钮状态图片
Ptr<Texture> imgOffNormal_, imgOnNormal_;
Ptr<Texture> imgOffHover_, imgOnHover_;
Ptr<Texture> imgOffPressed_, imgOnPressed_;
bool useStateImages_ = false;
// 边框 // 边框
Color borderColor_ = Color(0.6f, 0.6f, 0.6f, 1.0f); Color borderColor_ = Color(0.6f, 0.6f, 0.6f, 1.0f);
float borderWidth_ = 1.0f; float borderWidth_ = 1.0f;
@ -188,48 +269,10 @@ private:
bool hovered_ = false; bool hovered_ = false;
bool pressed_ = false; bool pressed_ = false;
Function<void()> onClick_; // 切换模式相关
}; bool toggleMode_ = false;
bool isOn_ = false;
// ============================================================================ Function<void(bool)> onStateChange_;
// 切换按钮 - 点击切换两种状态(支持图片和文字)
// ============================================================================
class ToggleImageButton : public Button {
public:
ToggleImageButton();
~ToggleImageButton() override = default;
static Ptr<ToggleImageButton> create();
// 设置两种状态的图片
void setStateImages(Ptr<Texture> stateOffNormal, Ptr<Texture> stateOnNormal,
Ptr<Texture> stateOffHover = nullptr,
Ptr<Texture> stateOnHover = nullptr,
Ptr<Texture> stateOffPressed = nullptr,
Ptr<Texture> stateOnPressed = nullptr);
// 设置两种状态的文字
void setStateText(const std::string &textOff, const std::string &textOn);
// 设置两种状态的文字颜色
void setStateTextColor(const Color &colorOff, const Color &colorOn);
// 获取/设置当前状态
bool isOn() const { return isOn_; }
void setOn(bool on);
void toggle();
// 设置状态改变回调
void setOnStateChange(Function<void(bool)> callback);
protected:
void onDrawWidget(RenderBackend &renderer) override;
private:
// 状态图片
Ptr<Texture> imgOffNormal_, imgOnNormal_;
Ptr<Texture> imgOffHover_, imgOnHover_;
Ptr<Texture> imgOffPressed_, imgOnPressed_;
// 状态文字 // 状态文字
std::string textOff_, textOn_; std::string textOff_, textOn_;
@ -240,8 +283,7 @@ private:
Color textColorOn_ = Colors::White; Color textColorOn_ = Colors::White;
bool useStateTextColor_ = false; bool useStateTextColor_ = false;
bool isOn_ = false; Function<void()> onClick_;
Function<void(bool)> onStateChange_;
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -364,11 +364,11 @@ Camera &Application::camera() { return *camera_; }
void Application::enterScene(Ptr<Scene> scene) { enterScene(scene, nullptr); } void Application::enterScene(Ptr<Scene> scene) { enterScene(scene, nullptr); }
void Application::enterScene(Ptr<Scene> scene, void Application::enterScene(Ptr<Scene> scene,
Ptr<class Transition> transition) { Ptr<class TransitionScene> transitionScene) {
if (sceneManager_ && scene) { if (sceneManager_ && scene) {
scene->setViewportSize(static_cast<float>(window_->getWidth()), scene->setViewportSize(static_cast<float>(window_->getWidth()),
static_cast<float>(window_->getHeight())); static_cast<float>(window_->getHeight()));
sceneManager_->enterScene(scene, transition); sceneManager_->enterScene(scene, transitionScene);
} }
} }

View File

@ -4,57 +4,16 @@
#include <extra2d/graphics/render_command.h> #include <extra2d/graphics/render_command.h>
#include <extra2d/platform/input.h> #include <extra2d/platform/input.h>
#include <extra2d/scene/scene_manager.h> #include <extra2d/scene/scene_manager.h>
#include <extra2d/scene/transition.h> #include <extra2d/scene/transition_box_scene.h>
#include <extra2d/scene/transition_fade_scene.h>
#include <extra2d/scene/transition_flip_scene.h>
#include <extra2d/scene/transition_scale_scene.h>
#include <extra2d/scene/transition_scene.h>
#include <extra2d/scene/transition_slide_scene.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
namespace extra2d { namespace extra2d {
// ============================================================================
// Transition 工厂映射 - 使用函数指针数组替代 switch
// ============================================================================
using TransitionFactory = Ptr<Transition> (*)(float);
static Ptr<Transition> createFadeTransition(float duration) {
return makePtr<FadeTransition>(duration);
}
static Ptr<Transition> createSlideLeftTransition(float duration) {
return makePtr<SlideTransition>(duration, TransitionDirection::Left);
}
static Ptr<Transition> createSlideRightTransition(float duration) {
return makePtr<SlideTransition>(duration, TransitionDirection::Right);
}
static Ptr<Transition> createSlideUpTransition(float duration) {
return makePtr<SlideTransition>(duration, TransitionDirection::Up);
}
static Ptr<Transition> createSlideDownTransition(float duration) {
return makePtr<SlideTransition>(duration, TransitionDirection::Down);
}
static Ptr<Transition> createScaleTransition(float duration) {
return makePtr<ScaleTransition>(duration);
}
static Ptr<Transition> createFlipTransition(float duration) {
return makePtr<FlipTransition>(duration);
}
// 工厂函数指针数组,索引对应 TransitionType 枚举值
static constexpr TransitionFactory TRANSITION_FACTORIES[] = {
createFadeTransition, // TransitionType::Fade = 0
createSlideLeftTransition, // TransitionType::SlideLeft = 1
createSlideRightTransition,// TransitionType::SlideRight = 2
createSlideUpTransition, // TransitionType::SlideUp = 3
createSlideDownTransition, // TransitionType::SlideDown = 4
createScaleTransition, // TransitionType::Scale = 5
createFlipTransition // TransitionType::Flip = 6
};
static constexpr size_t TRANSITION_FACTORY_COUNT = sizeof(TRANSITION_FACTORIES) / sizeof(TRANSITION_FACTORIES[0]);
namespace { namespace {
Node *hitTestTopmost(const Ptr<Node> &node, const Vec2 &worldPos) { Node *hitTestTopmost(const Ptr<Node> &node, const Vec2 &worldPos) {
@ -150,17 +109,22 @@ void SceneManager::enterScene(Ptr<Scene> scene) {
} }
void SceneManager::enterScene(Ptr<Scene> scene, void SceneManager::enterScene(Ptr<Scene> scene,
Ptr<class Transition> transition) { Ptr<TransitionScene> transitionScene) {
if (!scene || isTransitioning_) { if (!scene || isTransitioning_) {
return; return;
} }
if (!transition) { // 如果没有过渡场景,使用无过渡切换
if (!transitionScene) {
enterScene(scene); enterScene(scene);
return; return;
} }
auto current = getCurrentScene(); auto current = getCurrentScene();
if (!current) {
enterScene(scene);
return;
}
// 在过渡开始前,发送 UIHoverExit 给当前悬停的节点,重置按钮状态 // 在过渡开始前,发送 UIHoverExit 给当前悬停的节点,重置按钮状态
if (hoverTarget_) { if (hoverTarget_) {
@ -173,32 +137,39 @@ void SceneManager::enterScene(Ptr<Scene> scene,
captureTarget_ = nullptr; captureTarget_ = nullptr;
hasLastPointerWorld_ = false; hasLastPointerWorld_ = false;
transition->start(current, scene); // 设置过渡场景
outgoingScene_ = current; transitionScene->setOutScene(current);
incomingScene_ = scene; transitionScene->setFinishCallback([this]() { finishTransition(); });
activeTransition_ = transition;
currentTransition_ = TransitionType::None; // 暂停当前场景
current->pause();
// 推入过渡场景(作为中介场景)
transitionScene->onEnter();
transitionScene->onAttachToScene(transitionScene.get());
sceneStack_.push(transitionScene);
isTransitioning_ = true; isTransitioning_ = true;
transitionStackAction_ = [this]() { activeTransitionScene_ = transitionScene;
transitionStackAction_ = [this, transitionScene]() {
// 退出旧场景 // 退出旧场景
auto outgoing = outgoingScene_; auto outScene = transitionScene->getOutScene();
if (!sceneStack_.empty() && outgoing && sceneStack_.top() == outgoing) { if (!sceneStack_.empty() && outScene) {
outgoing->onExit(); // 过渡场景已经在栈顶,弹出它
outgoing->onDetachFromScene(); if (sceneStack_.top().get() == transitionScene.get()) {
sceneStack_.pop(); sceneStack_.pop();
}
// 推入新场景并调用 onEnter
auto incoming = incomingScene_;
if (incoming) {
sceneStack_.push(incoming);
if (!incoming->isRunning()) {
incoming->onEnter();
incoming->onAttachToScene(incoming.get());
} }
outScene->onExit();
outScene->onDetachFromScene();
}
// 推入新场景
auto inScene = transitionScene->getInScene();
if (inScene) {
inScene->onAttachToScene(inScene.get());
sceneStack_.push(inScene);
} }
}; };
// 注意:不在此处调用新场景的 onEnter由 transitionStackAction_
// 在过渡完成后调用
} }
void SceneManager::replaceScene(Ptr<Scene> scene, TransitionType transition, void SceneManager::replaceScene(Ptr<Scene> scene, TransitionType transition,
@ -216,19 +187,26 @@ void SceneManager::replaceScene(Ptr<Scene> scene, TransitionType transition,
startTransition(oldScene, scene, transition, duration, [this]() { startTransition(oldScene, scene, transition, duration, [this]() {
// 过渡完成后,退出旧场景并从堆栈中移除 // 过渡完成后,退出旧场景并从堆栈中移除
auto outgoing = outgoingScene_; if (!sceneStack_.empty() && activeTransitionScene_) {
auto incoming = incomingScene_; // 弹出过渡场景
if (!sceneStack_.empty() && outgoing && sceneStack_.top() == outgoing) { if (sceneStack_.top().get() == activeTransitionScene_.get()) {
outgoing->onExit(); sceneStack_.pop();
outgoing->onDetachFromScene(); }
sceneStack_.pop();
// 退出旧场景
auto outScene = activeTransitionScene_->getOutScene();
if (outScene) {
outScene->onExit();
outScene->onDetachFromScene();
}
} }
// 将新场景推入堆栈并调用 onEnter
if (incoming) { // 将新场景推入堆栈
sceneStack_.push(incoming); if (activeTransitionScene_) {
if (!incoming->isRunning()) { auto inScene = activeTransitionScene_->getInScene();
incoming->onEnter(); if (inScene) {
incoming->onAttachToScene(incoming.get()); inScene->onAttachToScene(inScene.get());
sceneStack_.push(inScene);
} }
} }
}); });
@ -267,12 +245,19 @@ void SceneManager::pushScene(Ptr<Scene> scene, TransitionType transition,
auto currentScene = sceneStack_.top(); auto currentScene = sceneStack_.top();
startTransition(currentScene, scene, transition, duration, [this]() { startTransition(currentScene, scene, transition, duration, [this]() {
// 过渡完成后,将新场景推入堆栈并调用 onEnter // 过渡完成后,将新场景推入堆栈
if (incomingScene_) { if (!sceneStack_.empty() && activeTransitionScene_) {
sceneStack_.push(incomingScene_); // 弹出过渡场景
if (!incomingScene_->isRunning()) { if (sceneStack_.top().get() == activeTransitionScene_.get()) {
incomingScene_->onEnter(); sceneStack_.pop();
incomingScene_->onAttachToScene(incomingScene_.get()); }
}
if (activeTransitionScene_) {
auto inScene = activeTransitionScene_->getInScene();
if (inScene) {
inScene->onAttachToScene(inScene.get());
sceneStack_.push(inScene);
} }
} }
}); });
@ -304,20 +289,26 @@ void SceneManager::popScene(TransitionType transition, float duration) {
startTransition(current, previous, transition, duration, [this]() { startTransition(current, previous, transition, duration, [this]() {
// 过渡完成后,退出当前场景并从堆栈中移除 // 过渡完成后,退出当前场景并从堆栈中移除
auto outgoing = outgoingScene_; if (!sceneStack_.empty() && activeTransitionScene_) {
if (!sceneStack_.empty() && outgoing && sceneStack_.top() == outgoing) { // 弹出过渡场景
outgoing->onExit(); if (sceneStack_.top().get() == activeTransitionScene_.get()) {
outgoing->onDetachFromScene(); sceneStack_.pop();
sceneStack_.pop(); }
}
// 恢复前一个场景 // 退出当前场景
auto incoming = incomingScene_; auto outScene = activeTransitionScene_->getOutScene();
if (!sceneStack_.empty() && incoming && sceneStack_.top() == incoming) { if (outScene) {
if (!incoming->isRunning()) { outScene->onExit();
incoming->onEnter(); outScene->onDetachFromScene();
incoming->onAttachToScene(incoming.get()); }
}
// 恢复前一个场景
if (activeTransitionScene_) {
auto inScene = activeTransitionScene_->getInScene();
if (inScene && !sceneStack_.empty() && sceneStack_.top() == inScene) {
inScene->resume();
} }
incoming->resume();
} }
}); });
} }
@ -349,18 +340,15 @@ void SceneManager::popToRootScene(TransitionType transition, float duration) {
startTransition(current, root, transition, duration, [this, root]() { startTransition(current, root, transition, duration, [this, root]() {
// 退出所有场景直到根场景 // 退出所有场景直到根场景
while (!sceneStack_.empty() && sceneStack_.top() != root) { while (!sceneStack_.empty() && sceneStack_.top().get() != root.get()) {
auto scene = sceneStack_.top(); auto scene = sceneStack_.top();
scene->onExit(); scene->onExit();
scene->onDetachFromScene(); scene->onDetachFromScene();
sceneStack_.pop(); sceneStack_.pop();
} }
// 恢复根场景 // 恢复根场景
if (!sceneStack_.empty() && sceneStack_.top() == root) { if (!sceneStack_.empty() && sceneStack_.top().get() == root.get()) {
if (!root->isRunning()) {
root->onEnter();
root->onAttachToScene(root.get());
}
root->resume(); root->resume();
} }
}); });
@ -409,12 +397,9 @@ void SceneManager::popToScene(const std::string &name,
scene->onDetachFromScene(); scene->onDetachFromScene();
sceneStack_.pop(); sceneStack_.pop();
} }
// 恢复目标场景 // 恢复目标场景
if (!sceneStack_.empty() && sceneStack_.top() == target) { if (!sceneStack_.empty() && sceneStack_.top() == target) {
if (!target->isRunning()) {
target->onEnter();
target->onAttachToScene(target.get());
}
target->resume(); target->resume();
} }
}); });
@ -479,7 +464,7 @@ bool SceneManager::hasScene(const std::string &name) const {
void SceneManager::update(float dt) { void SceneManager::update(float dt) {
if (isTransitioning_) { if (isTransitioning_) {
updateTransition(dt); // 过渡场景在栈顶,正常更新即可
hoverTarget_ = nullptr; hoverTarget_ = nullptr;
captureTarget_ = nullptr; captureTarget_ = nullptr;
hasLastPointerWorld_ = false; hasLastPointerWorld_ = false;
@ -496,13 +481,7 @@ void SceneManager::update(float dt) {
void SceneManager::render(RenderBackend &renderer) { void SceneManager::render(RenderBackend &renderer) {
Color clearColor = Colors::Black; Color clearColor = Colors::Black;
if (isTransitioning_) { if (!sceneStack_.empty()) {
if (outgoingScene_) {
clearColor = outgoingScene_->getBackgroundColor();
} else if (incomingScene_) {
clearColor = incomingScene_->getBackgroundColor();
}
} else if (!sceneStack_.empty()) {
clearColor = sceneStack_.top()->getBackgroundColor(); clearColor = sceneStack_.top()->getBackgroundColor();
} }
@ -510,10 +489,7 @@ void SceneManager::render(RenderBackend &renderer) {
clearColor.r, clearColor.g, clearColor.b); clearColor.r, clearColor.g, clearColor.b);
renderer.beginFrame(clearColor); renderer.beginFrame(clearColor);
if (isTransitioning_ && activeTransition_) { if (!sceneStack_.empty()) {
E2D_LOG_TRACE("SceneManager::render - rendering transition");
activeTransition_->render(renderer);
} else if (!sceneStack_.empty()) {
E2D_LOG_TRACE("SceneManager::render - rendering scene content"); E2D_LOG_TRACE("SceneManager::render - rendering scene content");
sceneStack_.top()->renderContent(renderer); sceneStack_.top()->renderContent(renderer);
} else { } else {
@ -525,13 +501,7 @@ void SceneManager::render(RenderBackend &renderer) {
} }
void SceneManager::collectRenderCommands(std::vector<RenderCommand> &commands) { void SceneManager::collectRenderCommands(std::vector<RenderCommand> &commands) {
if (isTransitioning_ && outgoingScene_) { if (!sceneStack_.empty()) {
// During transition, collect commands from both scenes
outgoingScene_->collectRenderCommands(commands, 0);
if (incomingScene_) {
incomingScene_->collectRenderCommands(commands, 0);
}
} else if (!sceneStack_.empty()) {
sceneStack_.top()->collectRenderCommands(commands, 0); sceneStack_.top()->collectRenderCommands(commands, 0);
} }
} }
@ -555,18 +525,14 @@ void SceneManager::startTransition(Ptr<Scene> from, Ptr<Scene> to,
return; return;
} }
// 使用工厂映射替代 switch // 创建过渡场景
Ptr<Transition> transition; auto transitionScene = createTransitionScene(type, duration, to);
size_t typeIndex = static_cast<size_t>(type); if (!transitionScene) {
if (typeIndex < TRANSITION_FACTORY_COUNT) { // 回退到无过渡切换
transition = TRANSITION_FACTORIES[typeIndex](duration); replaceScene(to);
} else { return;
// 默认使用 Fade 过渡
transition = TRANSITION_FACTORIES[0](duration);
} }
transition->start(from, to);
// 在过渡开始前,发送 UIHoverExit 给当前悬停的节点,重置按钮状态 // 在过渡开始前,发送 UIHoverExit 给当前悬停的节点,重置按钮状态
if (hoverTarget_) { if (hoverTarget_) {
Event evt; Event evt;
@ -578,29 +544,55 @@ void SceneManager::startTransition(Ptr<Scene> from, Ptr<Scene> to,
captureTarget_ = nullptr; captureTarget_ = nullptr;
hasLastPointerWorld_ = false; hasLastPointerWorld_ = false;
// 设置过渡场景
transitionScene->setOutScene(from);
transitionScene->setFinishCallback([this]() { finishTransition(); });
// 暂停当前场景
from->pause();
// 推入过渡场景(作为中介场景)
transitionScene->onEnter();
transitionScene->onAttachToScene(transitionScene.get());
sceneStack_.push(transitionScene);
isTransitioning_ = true; isTransitioning_ = true;
currentTransition_ = type; currentTransition_ = type;
transitionDuration_ = duration; activeTransitionScene_ = transitionScene;
transitionElapsed_ = 0.0f;
outgoingScene_ = from;
incomingScene_ = to;
activeTransition_ = transition;
transitionStackAction_ = std::move(stackAction); transitionStackAction_ = std::move(stackAction);
// 注意:不在此处调用新场景的 onEnter由 transitionStackAction_
// 在过渡完成后调用
} }
void SceneManager::updateTransition(float dt) { Ptr<TransitionScene> SceneManager::createTransitionScene(TransitionType type,
transitionElapsed_ += dt; float duration,
Ptr<Scene> inScene) {
if (!inScene) {
return nullptr;
}
if (activeTransition_) { switch (type) {
activeTransition_->update(dt); case TransitionType::Fade:
if (activeTransition_->isFinished()) { return TransitionFadeScene::create(duration, inScene);
finishTransition(); case TransitionType::SlideLeft:
} return TransitionSlideScene::create(duration, inScene,
} else { TransitionDirection::Left);
finishTransition(); case TransitionType::SlideRight:
return TransitionSlideScene::create(duration, inScene,
TransitionDirection::Right);
case TransitionType::SlideUp:
return TransitionSlideScene::create(duration, inScene,
TransitionDirection::Up);
case TransitionType::SlideDown:
return TransitionSlideScene::create(duration, inScene,
TransitionDirection::Down);
case TransitionType::Scale:
return TransitionScaleScene::create(duration, inScene);
case TransitionType::Flip:
return TransitionFlipScene::create(duration, inScene);
case TransitionType::Box:
return TransitionBoxScene::create(duration, inScene);
default:
// 默认使用淡入淡出
return TransitionFadeScene::create(duration, inScene);
} }
} }
@ -625,9 +617,7 @@ void SceneManager::finishTransition() {
dispatchToNode(lastHoverTarget, evt); dispatchToNode(lastHoverTarget, evt);
} }
outgoingScene_.reset(); activeTransitionScene_.reset();
incomingScene_.reset();
activeTransition_.reset();
transitionStackAction_ = nullptr; transitionStackAction_ = nullptr;
if (transitionCallback_) { if (transitionCallback_) {

View File

@ -1,396 +0,0 @@
#include <algorithm>
#include <extra2d/core/math_types.h>
#include <extra2d/graphics/camera.h>
#include <extra2d/graphics/render_backend.h>
#include <extra2d/scene/transition.h>
#include <glm/gtc/matrix_transform.hpp>
namespace extra2d {
// ============================================================================
// 缓动函数
// ============================================================================
static float easeInOutQuad(float t) {
return t < 0.5f ? 2.0f * t * t : -1.0f + (4.0f - 2.0f * t) * t;
}
static float easeOutQuad(float t) { return t * (2.0f - t); }
// ============================================================================
// Transition 基类
// ============================================================================
Transition::Transition(float duration)
: duration_(duration), elapsed_(0.0f), progress_(0.0f), isFinished_(false),
isStarted_(false) {}
void Transition::start(Ptr<Scene> from, Ptr<Scene> to) {
outgoingScene_ = from;
incomingScene_ = to;
elapsed_ = 0.0f;
progress_ = 0.0f;
isFinished_ = false;
isStarted_ = true;
}
void Transition::update(float dt) {
if (!isStarted_ || isFinished_) {
return;
}
elapsed_ += dt;
progress_ = duration_ > 0.0f ? std::min(1.0f, elapsed_ / duration_) : 1.0f;
if (progress_ >= 1.0f) {
onFinish();
}
}
void Transition::render(RenderBackend &renderer) {
if (!isStarted_ || isFinished_) {
return;
}
onRenderTransition(renderer, easeInOutQuad(progress_));
}
float Transition::getFadeInAlpha() const { return easeOutQuad(progress_); }
float Transition::getFadeOutAlpha() const {
return 1.0f - easeOutQuad(progress_);
}
void Transition::onFinish() {
isFinished_ = true;
if (finishCallback_) {
finishCallback_();
}
}
// ============================================================================
// FadeTransition - 淡入淡出
// ============================================================================
FadeTransition::FadeTransition(float duration) : Transition(duration) {}
void FadeTransition::onRenderTransition(RenderBackend &renderer,
float progress) {
float screenWidth = 800.0f;
float screenHeight = 600.0f;
if (outgoingScene_) {
Size viewportSize = outgoingScene_->getViewportSize();
if (viewportSize.width > 0 && viewportSize.height > 0) {
screenWidth = viewportSize.width;
screenHeight = viewportSize.height;
}
} else if (incomingScene_) {
Size viewportSize = incomingScene_->getViewportSize();
if (viewportSize.width > 0 && viewportSize.height > 0) {
screenWidth = viewportSize.width;
screenHeight = viewportSize.height;
}
}
glm::mat4 overlayVP =
glm::ortho(0.0f, screenWidth, screenHeight, 0.0f, -1.0f, 1.0f);
if (progress < 0.5f) {
if (outgoingScene_) {
outgoingScene_->renderContent(renderer);
}
float a = std::clamp(progress * 2.0f, 0.0f, 1.0f);
renderer.setViewProjection(overlayVP);
renderer.fillRect(Rect(0.0f, 0.0f, screenWidth, screenHeight),
Color(0.0f, 0.0f, 0.0f, a));
} else {
if (incomingScene_) {
incomingScene_->renderContent(renderer);
}
float a = std::clamp((1.0f - progress) * 2.0f, 0.0f, 1.0f);
renderer.setViewProjection(overlayVP);
renderer.fillRect(Rect(0.0f, 0.0f, screenWidth, screenHeight),
Color(0.0f, 0.0f, 0.0f, a));
}
}
// ============================================================================
// SlideTransition - 滑动
// ============================================================================
SlideTransition::SlideTransition(float duration, TransitionDirection direction)
: Transition(duration), direction_(direction) {}
void SlideTransition::onRenderTransition(RenderBackend &renderer,
float progress) {
// 获取视口尺寸
float screenWidth = 800.0f;
float screenHeight = 600.0f;
if (outgoingScene_) {
Size viewportSize = outgoingScene_->getViewportSize();
if (viewportSize.width > 0 && viewportSize.height > 0) {
screenWidth = viewportSize.width;
screenHeight = viewportSize.height;
}
} else if (incomingScene_) {
Size viewportSize = incomingScene_->getViewportSize();
if (viewportSize.width > 0 && viewportSize.height > 0) {
screenWidth = viewportSize.width;
screenHeight = viewportSize.height;
}
}
// 渲染源场景(滑出)
if (outgoingScene_) {
float offsetX = 0.0f;
float offsetY = 0.0f;
switch (direction_) {
case TransitionDirection::Left:
offsetX = -screenWidth * progress;
break;
case TransitionDirection::Right:
offsetX = screenWidth * progress;
break;
case TransitionDirection::Up:
offsetY = -screenHeight * progress;
break;
case TransitionDirection::Down:
offsetY = screenHeight * progress;
break;
}
// 保存原始相机位置
Camera *camera = outgoingScene_->getActiveCamera();
Vec2 originalPos = camera ? camera->getPosition() : Vec2::Zero();
// 应用偏移
if (camera) {
camera->setPosition(originalPos.x + offsetX, originalPos.y + offsetY);
}
// 渲染场景
outgoingScene_->renderContent(renderer);
// 恢复相机位置
if (camera) {
camera->setPosition(originalPos);
}
}
// 渲染目标场景(滑入)
if (incomingScene_) {
float offsetX = 0.0f;
float offsetY = 0.0f;
switch (direction_) {
case TransitionDirection::Left:
offsetX = screenWidth * (1.0f - progress);
break;
case TransitionDirection::Right:
offsetX = -screenWidth * (1.0f - progress);
break;
case TransitionDirection::Up:
offsetY = screenHeight * (1.0f - progress);
break;
case TransitionDirection::Down:
offsetY = -screenHeight * (1.0f - progress);
break;
}
// 保存原始相机位置
Camera *camera = incomingScene_->getActiveCamera();
Vec2 originalPos = camera ? camera->getPosition() : Vec2::Zero();
// 应用偏移
if (camera) {
camera->setPosition(originalPos.x + offsetX, originalPos.y + offsetY);
}
// 渲染场景
incomingScene_->renderContent(renderer);
// 恢复相机位置
if (camera) {
camera->setPosition(originalPos);
}
}
}
// ============================================================================
// ScaleTransition - 缩放
// ============================================================================
ScaleTransition::ScaleTransition(float duration) : Transition(duration) {}
void ScaleTransition::onRenderTransition(RenderBackend &renderer,
float progress) {
// 源场景:缩小消失
if (outgoingScene_) {
float scale = std::max(0.01f, 1.0f - progress);
// 保存原始相机状态
Camera *camera = outgoingScene_->getActiveCamera();
float originalZoom = camera ? camera->getZoom() : 1.0f;
Vec2 originalPos = camera ? camera->getPosition() : Vec2::Zero();
// 应用缩放(通过调整相机 zoom 实现)
if (camera) {
camera->setZoom(originalZoom * scale);
}
// 渲染场景
outgoingScene_->renderContent(renderer);
// 恢复相机状态
if (camera) {
camera->setZoom(originalZoom);
camera->setPosition(originalPos);
}
}
// 目标场景:放大出现
if (incomingScene_) {
float scale = std::max(0.01f, progress);
// 保存原始相机状态
Camera *camera = incomingScene_->getActiveCamera();
float originalZoom = camera ? camera->getZoom() : 1.0f;
Vec2 originalPos = camera ? camera->getPosition() : Vec2::Zero();
// 应用缩放
if (camera) {
camera->setZoom(originalZoom * scale);
}
// 渲染场景
incomingScene_->renderContent(renderer);
// 恢复相机状态
if (camera) {
camera->setZoom(originalZoom);
camera->setPosition(originalPos);
}
}
}
// ============================================================================
// FlipTransition - 翻页
// ============================================================================
FlipTransition::FlipTransition(float duration, Axis axis)
: Transition(duration), axis_(axis) {}
void FlipTransition::onRenderTransition(RenderBackend &renderer,
float progress) {
float angle = progress * PI_F; // 180度翻转
if (progress < 0.5f) {
// 前半段:翻转源场景
if (outgoingScene_) {
float currentAngle = angle;
// 保存原始相机状态
Camera *camera = outgoingScene_->getActiveCamera();
float originalRotation = camera ? camera->getRotation() : 0.0f;
// 应用旋转水平翻转绕Y轴垂直翻转绕X轴
if (axis_ == Axis::Horizontal) {
// 水平轴翻转 - 模拟绕X轴旋转
if (camera) {
camera->setRotation(originalRotation + currentAngle * RAD_TO_DEG);
}
} else {
// 垂直轴翻转 - 模拟绕Y轴旋转
if (camera) {
camera->setRotation(originalRotation - currentAngle * RAD_TO_DEG);
}
}
// 渲染场景
outgoingScene_->renderContent(renderer);
// 恢复相机状态
if (camera) {
camera->setRotation(originalRotation);
}
}
} else {
// 后半段:翻转目标场景
if (incomingScene_) {
float currentAngle = angle - PI_F;
// 保存原始相机状态
Camera *camera = incomingScene_->getActiveCamera();
float originalRotation = camera ? camera->getRotation() : 0.0f;
// 应用旋转
if (axis_ == Axis::Horizontal) {
if (camera) {
camera->setRotation(originalRotation + currentAngle * RAD_TO_DEG);
}
} else {
if (camera) {
camera->setRotation(originalRotation - currentAngle * RAD_TO_DEG);
}
}
// 渲染场景
incomingScene_->renderContent(renderer);
// 恢复相机状态
if (camera) {
camera->setRotation(originalRotation);
}
}
}
}
// ============================================================================
// BoxTransition - 方块过渡
// ============================================================================
BoxTransition::BoxTransition(float duration, int divisions)
: Transition(duration), divisions_(divisions) {}
void BoxTransition::onRenderTransition(RenderBackend &renderer,
float progress) {
float screenWidth = 800.0f;
float screenHeight = 600.0f;
if (incomingScene_) {
Size viewportSize = incomingScene_->getViewportSize();
if (viewportSize.width > 0 && viewportSize.height > 0) {
screenWidth = viewportSize.width;
screenHeight = viewportSize.height;
}
} else if (outgoingScene_) {
Size viewportSize = outgoingScene_->getViewportSize();
if (viewportSize.width > 0 && viewportSize.height > 0) {
screenWidth = viewportSize.width;
screenHeight = viewportSize.height;
}
}
if (incomingScene_) {
incomingScene_->renderContent(renderer);
} else if (outgoingScene_) {
outgoingScene_->renderContent(renderer);
} else {
return;
}
int div = std::max(1, divisions_);
int total = div * div;
int visible = std::clamp(static_cast<int>(total * progress), 0, total);
float cellW = screenWidth / static_cast<float>(div);
float cellH = screenHeight / static_cast<float>(div);
glm::mat4 overlayVP =
glm::ortho(0.0f, screenWidth, screenHeight, 0.0f, -1.0f, 1.0f);
renderer.setViewProjection(overlayVP);
for (int idx = visible; idx < total; ++idx) {
int x = idx % div;
int y = idx / div;
renderer.fillRect(Rect(x * cellW, y * cellH, cellW + 1.0f, cellH + 1.0f),
Colors::Black);
}
}
} // namespace extra2d

View File

@ -0,0 +1,72 @@
#include <extra2d/scene/transition_box_scene.h>
#include <extra2d/app/application.h>
#include <extra2d/graphics/render_backend.h>
#include <extra2d/core/color.h>
#include <glm/gtc/matrix_transform.hpp>
#include <algorithm>
namespace extra2d {
TransitionBoxScene::TransitionBoxScene(float duration, Ptr<Scene> inScene,
int divisions)
: TransitionScene(duration, inScene), divisions_(divisions) {}
Ptr<TransitionBoxScene> TransitionBoxScene::create(float duration,
Ptr<Scene> inScene,
int divisions) {
return makePtr<TransitionBoxScene>(duration, inScene, divisions);
}
void TransitionBoxScene::onTransitionStart() {
// 方块过渡不需要特殊的初始化
}
void TransitionBoxScene::renderContent(RenderBackend &renderer) {
// 获取窗口大小
auto &app = Application::instance();
float windowWidth = static_cast<float>(app.window().getWidth());
float windowHeight = static_cast<float>(app.window().getHeight());
// 更新进度
elapsed_ += 1.0f / 60.0f;
progress_ = duration_ > 0.0f ? std::min(1.0f, elapsed_ / duration_) : 1.0f;
// 先渲染新场景
if (inScene_) {
inScene_->renderContent(renderer);
} else if (outScene_) {
outScene_->renderContent(renderer);
}
// 设置视口为整个窗口
renderer.setViewport(0, 0, static_cast<int>(windowWidth),
static_cast<int>(windowHeight));
// 计算要显示的方块数量
int div = std::max(1, divisions_);
int total = div * div;
int visible = std::clamp(static_cast<int>(total * progress_), 0, total);
float cellW = windowWidth / static_cast<float>(div);
float cellH = windowHeight / static_cast<float>(div);
// 设置正交投影
glm::mat4 overlayVP =
glm::ortho(0.0f, windowWidth, windowHeight, 0.0f, -1.0f, 1.0f);
renderer.setViewProjection(overlayVP);
// 绘制剩余的方块(作为遮罩)
for (int idx = visible; idx < total; ++idx) {
int x = idx % div;
int y = idx / div;
renderer.fillRect(Rect(x * cellW, y * cellH, cellW + 1.0f, cellH + 1.0f),
Colors::Black);
}
// 检查是否完成
if (progress_ >= 1.0f && !isFinished_) {
finish();
}
}
} // namespace extra2d

View File

@ -0,0 +1,88 @@
#include <extra2d/scene/transition_fade_scene.h>
#include <extra2d/app/application.h>
#include <extra2d/graphics/render_backend.h>
#include <extra2d/graphics/render_target.h>
#include <extra2d/utils/logger.h>
#include <glm/gtc/matrix_transform.hpp>
namespace extra2d {
TransitionFadeScene::TransitionFadeScene(float duration, Ptr<Scene> inScene,
const Color &color)
: TransitionScene(duration, inScene), maskColor_(color) {}
Ptr<TransitionFadeScene> TransitionFadeScene::create(float duration,
Ptr<Scene> inScene,
const Color &color) {
return makePtr<TransitionFadeScene>(duration, inScene, color);
}
void TransitionFadeScene::onTransitionStart() {
E2D_LOG_DEBUG("TransitionFadeScene::onTransitionStart - 启动淡入淡出过渡");
// 使用一个定时器来更新进度
// 由于我们没有直接的动作系统集成到 Scene使用简单的 update 逻辑
// 实际进度更新由 SceneManager 的 update 驱动
}
void TransitionFadeScene::renderContent(RenderBackend &renderer) {
// 获取窗口大小
auto &app = Application::instance();
float windowWidth = static_cast<float>(app.window().getWidth());
float windowHeight = static_cast<float>(app.window().getHeight());
// 计算当前进度
elapsed_ += 1.0f / 60.0f; // 假设 60fps实际应该由 update 传递 dt
progress_ = duration_ > 0.0f ? std::min(1.0f, elapsed_ / duration_) : 1.0f;
// 检查是否需要切换场景(进度过半时)
if (!hasSwitched_ && progress_ >= 0.5f) {
hideOutShowIn();
}
// 根据进度渲染
if (progress_ < 0.5f) {
// 第一阶段:显示旧场景
drawOutScene(renderer);
} else {
// 第二阶段:显示新场景
drawInScene(renderer);
}
// 绘制遮罩层
// 计算遮罩透明度
float maskAlpha;
if (progress_ < 0.5f) {
// 前半段:从透明到不透明
maskAlpha = progress_ * 2.0f; // 0 -> 1
} else {
// 后半段:从不透明到透明
maskAlpha = (1.0f - progress_) * 2.0f; // 1 -> 0
}
// 设置视口为整个窗口
renderer.setViewport(0, 0, static_cast<int>(windowWidth),
static_cast<int>(windowHeight));
// 设置正交投影
glm::mat4 overlayVP =
glm::ortho(0.0f, windowWidth, windowHeight, 0.0f, -1.0f, 1.0f);
renderer.setViewProjection(overlayVP);
// 绘制遮罩
Color maskColor = maskColor_;
maskColor.a = maskAlpha;
renderer.fillRect(Rect(0.0f, 0.0f, windowWidth, windowHeight), maskColor);
// 检查是否完成
if (progress_ >= 1.0f && !isFinished_) {
finish();
}
}
void TransitionFadeScene::hideOutShowIn() {
hasSwitched_ = true;
E2D_LOG_DEBUG("TransitionFadeScene::hideOutShowIn - 切换场景显示");
}
} // namespace extra2d

View File

@ -0,0 +1,91 @@
#include <extra2d/scene/transition_flip_scene.h>
#include <extra2d/graphics/camera.h>
#include <extra2d/graphics/render_backend.h>
#include <extra2d/core/math_types.h>
namespace extra2d {
TransitionFlipScene::TransitionFlipScene(float duration, Ptr<Scene> inScene,
Axis axis)
: TransitionScene(duration, inScene), axis_(axis) {}
Ptr<TransitionFlipScene> TransitionFlipScene::create(float duration,
Ptr<Scene> inScene,
Axis axis) {
return makePtr<TransitionFlipScene>(duration, inScene, axis);
}
void TransitionFlipScene::onTransitionStart() {
// 翻页过渡不需要特殊的初始化
}
void TransitionFlipScene::renderContent(RenderBackend &renderer) {
// 更新进度
elapsed_ += 1.0f / 60.0f;
progress_ = duration_ > 0.0f ? std::min(1.0f, elapsed_ / duration_) : 1.0f;
// 缓动函数
float easeProgress = progress_ < 0.5f ? 2.0f * progress_ * progress_
: -1.0f + (4.0f - 2.0f * progress_) * progress_;
float angle = easeProgress * PI_F; // 180度翻转
if (progress_ < 0.5f) {
// 前半段:翻转源场景
if (outScene_) {
float currentAngle = angle;
Camera *camera = outScene_->getActiveCamera();
float originalRotation = camera ? camera->getRotation() : 0.0f;
if (axis_ == Axis::Horizontal) {
// 水平轴翻转 - 模拟绕X轴旋转
if (camera) {
camera->setRotation(originalRotation + currentAngle * RAD_TO_DEG);
}
} else {
// 垂直轴翻转 - 模拟绕Y轴旋转
if (camera) {
camera->setRotation(originalRotation - currentAngle * RAD_TO_DEG);
}
}
outScene_->renderContent(renderer);
if (camera) {
camera->setRotation(originalRotation);
}
}
} else {
// 后半段:翻转目标场景
if (inScene_) {
float currentAngle = angle - PI_F;
Camera *camera = inScene_->getActiveCamera();
float originalRotation = camera ? camera->getRotation() : 0.0f;
if (axis_ == Axis::Horizontal) {
if (camera) {
camera->setRotation(originalRotation + currentAngle * RAD_TO_DEG);
}
} else {
if (camera) {
camera->setRotation(originalRotation - currentAngle * RAD_TO_DEG);
}
}
inScene_->renderContent(renderer);
if (camera) {
camera->setRotation(originalRotation);
}
}
}
// 检查是否完成
if (progress_ >= 1.0f && !isFinished_) {
finish();
}
}
} // namespace extra2d

View File

@ -0,0 +1,71 @@
#include <extra2d/scene/transition_scale_scene.h>
#include <extra2d/graphics/camera.h>
#include <extra2d/graphics/render_backend.h>
#include <algorithm>
namespace extra2d {
TransitionScaleScene::TransitionScaleScene(float duration, Ptr<Scene> inScene)
: TransitionScene(duration, inScene) {}
Ptr<TransitionScaleScene> TransitionScaleScene::create(float duration,
Ptr<Scene> inScene) {
return makePtr<TransitionScaleScene>(duration, inScene);
}
void TransitionScaleScene::onTransitionStart() {
// 缩放过渡不需要特殊的初始化
}
void TransitionScaleScene::renderContent(RenderBackend &renderer) {
// 更新进度
elapsed_ += 1.0f / 60.0f;
progress_ = duration_ > 0.0f ? std::min(1.0f, elapsed_ / duration_) : 1.0f;
// 缓动函数
float easeProgress = progress_ < 0.5f ? 2.0f * progress_ * progress_
: -1.0f + (4.0f - 2.0f * progress_) * progress_;
// 源场景:缩小消失
if (outScene_) {
float scale = std::max(0.01f, 1.0f - easeProgress);
Camera *camera = outScene_->getActiveCamera();
float originalZoom = camera ? camera->getZoom() : 1.0f;
if (camera) {
camera->setZoom(originalZoom * scale);
}
outScene_->renderContent(renderer);
if (camera) {
camera->setZoom(originalZoom);
}
}
// 目标场景:放大出现
if (inScene_) {
float scale = std::max(0.01f, easeProgress);
Camera *camera = inScene_->getActiveCamera();
float originalZoom = camera ? camera->getZoom() : 1.0f;
if (camera) {
camera->setZoom(originalZoom * scale);
}
inScene_->renderContent(renderer);
if (camera) {
camera->setZoom(originalZoom);
}
}
// 检查是否完成
if (progress_ >= 1.0f && !isFinished_) {
finish();
}
}
} // namespace extra2d

View File

@ -0,0 +1,81 @@
#include <extra2d/scene/transition_scene.h>
#include <extra2d/graphics/render_backend.h>
#include <extra2d/utils/logger.h>
namespace extra2d {
TransitionScene::TransitionScene(float duration, Ptr<Scene> inScene)
: duration_(duration), inScene_(inScene) {}
void TransitionScene::onEnter() {
// 调用基类的 onEnter
Scene::onEnter();
// 调用退出场景的 onExitTransitionDidStart
if (outScene_) {
outScene_->onExitTransitionDidStart();
}
// 调用进入场景的 onEnter
if (inScene_) {
inScene_->onEnter();
inScene_->onAttachToScene(inScene_.get());
}
// 启动过渡
onTransitionStart();
}
void TransitionScene::onExit() {
// 调用退出场景的 onExit
if (outScene_) {
outScene_->onExit();
outScene_->onDetachFromScene();
}
// 调用进入场景的 onEnterTransitionDidFinish
if (inScene_) {
inScene_->onEnterTransitionDidFinish();
}
// 调用基类的 onExit
Scene::onExit();
}
void TransitionScene::finish() {
if (isFinished_) {
return;
}
isFinished_ = true;
E2D_LOG_DEBUG("TransitionScene::finish - 过渡完成,切换到目标场景");
// 调用完成回调,通知 SceneManager 进行场景切换
if (finishCallback_) {
finishCallback_();
}
}
void TransitionScene::renderContent(RenderBackend &renderer) {
// 在 TransitionScene 上渲染新旧两个子场景
// 子类可以重写此方法来控制渲染顺序和效果
// 默认先渲染退出场景,再渲染进入场景(在上方)
drawOutScene(renderer);
drawInScene(renderer);
}
void TransitionScene::drawOutScene(RenderBackend &renderer) {
if (outScene_) {
outScene_->renderContent(renderer);
}
}
void TransitionScene::drawInScene(RenderBackend &renderer) {
if (inScene_) {
inScene_->renderContent(renderer);
}
}
} // namespace extra2d

View File

@ -0,0 +1,121 @@
#include <extra2d/scene/transition_slide_scene.h>
#include <extra2d/graphics/camera.h>
#include <extra2d/graphics/render_backend.h>
namespace extra2d {
TransitionSlideScene::TransitionSlideScene(float duration, Ptr<Scene> inScene,
TransitionDirection direction)
: TransitionScene(duration, inScene), direction_(direction) {}
Ptr<TransitionSlideScene> TransitionSlideScene::create(
float duration, Ptr<Scene> inScene, TransitionDirection direction) {
return makePtr<TransitionSlideScene>(duration, inScene, direction);
}
void TransitionSlideScene::onTransitionStart() {
// 滑动过渡不需要特殊的初始化
}
void TransitionSlideScene::renderContent(RenderBackend &renderer) {
// 获取视口尺寸
float screenWidth = 800.0f;
float screenHeight = 600.0f;
if (outScene_) {
Size viewportSize = outScene_->getViewportSize();
if (viewportSize.width > 0 && viewportSize.height > 0) {
screenWidth = viewportSize.width;
screenHeight = viewportSize.height;
}
} else if (inScene_) {
Size viewportSize = inScene_->getViewportSize();
if (viewportSize.width > 0 && viewportSize.height > 0) {
screenWidth = viewportSize.width;
screenHeight = viewportSize.height;
}
}
// 更新进度
elapsed_ += 1.0f / 60.0f;
progress_ = duration_ > 0.0f ? std::min(1.0f, elapsed_ / duration_) : 1.0f;
// 缓动函数
float easeProgress = progress_ < 0.5f ? 2.0f * progress_ * progress_
: -1.0f + (4.0f - 2.0f * progress_) * progress_;
// 渲染退出场景(滑出)
if (outScene_) {
float offsetX = 0.0f;
float offsetY = 0.0f;
switch (direction_) {
case TransitionDirection::Left:
offsetX = -screenWidth * easeProgress;
break;
case TransitionDirection::Right:
offsetX = screenWidth * easeProgress;
break;
case TransitionDirection::Up:
offsetY = -screenHeight * easeProgress;
break;
case TransitionDirection::Down:
offsetY = screenHeight * easeProgress;
break;
}
Camera *camera = outScene_->getActiveCamera();
Vec2 originalPos = camera ? camera->getPosition() : Vec2::Zero();
if (camera) {
camera->setPosition(originalPos.x + offsetX, originalPos.y + offsetY);
}
outScene_->renderContent(renderer);
if (camera) {
camera->setPosition(originalPos);
}
}
// 渲染进入场景(滑入)
if (inScene_) {
float offsetX = 0.0f;
float offsetY = 0.0f;
switch (direction_) {
case TransitionDirection::Left:
offsetX = screenWidth * (1.0f - easeProgress);
break;
case TransitionDirection::Right:
offsetX = -screenWidth * (1.0f - easeProgress);
break;
case TransitionDirection::Up:
offsetY = screenHeight * (1.0f - easeProgress);
break;
case TransitionDirection::Down:
offsetY = -screenHeight * (1.0f - easeProgress);
break;
}
Camera *camera = inScene_->getActiveCamera();
Vec2 originalPos = camera ? camera->getPosition() : Vec2::Zero();
if (camera) {
camera->setPosition(originalPos.x + offsetX, originalPos.y + offsetY);
}
inScene_->renderContent(renderer);
if (camera) {
camera->setPosition(originalPos);
}
}
// 检查是否完成
if (progress_ >= 1.0f && !isFinished_) {
finish();
}
}
} // namespace extra2d

View File

@ -1,9 +1,9 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <extra2d/app/application.h> #include <extra2d/app/application.h>
#include <extra2d/core/string.h>
#include <extra2d/graphics/render_backend.h> #include <extra2d/graphics/render_backend.h>
#include <extra2d/ui/button.h> #include <extra2d/ui/button.h>
#include <extra2d/core/string.h>
namespace extra2d { namespace extra2d {
@ -37,21 +37,21 @@ Button::Button() {
dispatcher.addListener(EventType::UIReleased, dispatcher.addListener(EventType::UIReleased,
[this](Event &) { pressed_ = false; }); [this](Event &) { pressed_ = false; });
dispatcher.addListener(EventType::UIClicked, [this](Event &) { dispatcher.addListener(EventType::UIClicked, [this](Event &) {
if (onClick_) if (toggleMode_) {
toggle();
}
if (onClick_) {
onClick_(); onClick_();
}
}); });
} }
Button::Button(const std::string &text) : Button() { Button::Button(const std::string &text) : Button() { text_ = text; }
text_ = text;
}
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 静态创建方法 // 静态创建方法
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
Ptr<Button> Button::create() { Ptr<Button> Button::create() { return makePtr<Button>(); }
return makePtr<Button>();
}
Ptr<Button> Button::create(const std::string &text) { Ptr<Button> Button::create(const std::string &text) {
return makePtr<Button>(text); return makePtr<Button>(text);
@ -197,9 +197,7 @@ void Button::setPadding(const Vec2 &padding) {
} }
} }
void Button::setTextColor(const Color &color) { void Button::setTextColor(const Color &color) { textColor_ = color; }
textColor_ = color;
}
void Button::setBackgroundColor(const Color &normal, const Color &hover, void Button::setBackgroundColor(const Color &normal, const Color &hover,
const Color &pressed) { const Color &pressed) {
@ -229,10 +227,38 @@ void Button::setOnClick(Function<void()> callback) {
onClick_ = std::move(callback); onClick_ = std::move(callback);
} }
void Button::setHoverCursor(CursorShape cursor) { void Button::setToggleMode(bool enabled) { toggleMode_ = enabled; }
hoverCursor_ = cursor;
void Button::setOn(bool on) {
if (isOn_ != on) {
isOn_ = on;
if (onStateChange_) {
onStateChange_(isOn_);
}
}
} }
void Button::toggle() { setOn(!isOn_); }
void Button::setOnStateChange(Function<void(bool)> callback) {
onStateChange_ = std::move(callback);
}
void Button::setStateText(const std::string &textOff,
const std::string &textOn) {
textOff_ = textOff;
textOn_ = textOn;
useStateText_ = true;
}
void Button::setStateTextColor(const Color &colorOff, const Color &colorOn) {
textColorOff_ = colorOff;
textColorOn_ = colorOn;
useStateTextColor_ = true;
}
void Button::setHoverCursor(CursorShape cursor) { hoverCursor_ = cursor; }
void Button::setBackgroundImage(Ptr<Texture> normal, Ptr<Texture> hover, void Button::setBackgroundImage(Ptr<Texture> normal, Ptr<Texture> hover,
Ptr<Texture> pressed) { Ptr<Texture> pressed) {
imgNormal_ = normal; imgNormal_ = normal;
@ -256,12 +282,33 @@ void Button::setBackgroundImage(Ptr<Texture> texture, const Rect &rect) {
imgPressedRect_ = rect; imgPressedRect_ = rect;
useImageBackground_ = (texture != nullptr); useImageBackground_ = (texture != nullptr);
useTextureRect_ = true; useTextureRect_ = true;
useStateImages_ = false;
if (useImageBackground_ && scaleMode_ == ImageScaleMode::Original) { if (useImageBackground_ && scaleMode_ == ImageScaleMode::Original) {
setSize(rect.size.width, rect.size.height); setSize(rect.size.width, rect.size.height);
} }
} }
void Button::setStateBackgroundImage(
Ptr<Texture> offNormal, Ptr<Texture> onNormal, Ptr<Texture> offHover,
Ptr<Texture> onHover, Ptr<Texture> offPressed, Ptr<Texture> onPressed) {
imgOffNormal_ = offNormal;
imgOnNormal_ = onNormal;
imgOffHover_ = offHover ? offHover : offNormal;
imgOnHover_ = onHover ? onHover : onNormal;
imgOffPressed_ = offPressed ? offPressed : offNormal;
imgOnPressed_ = onPressed ? onPressed : onNormal;
useStateImages_ = (offNormal != nullptr && onNormal != nullptr);
useImageBackground_ = useStateImages_;
useTextureRect_ = false;
if (useStateImages_ && scaleMode_ == ImageScaleMode::Original && offNormal) {
setSize(static_cast<float>(offNormal->getWidth()),
static_cast<float>(offNormal->getHeight()));
}
}
void Button::setBackgroundImageScaleMode(ImageScaleMode mode) { void Button::setBackgroundImageScaleMode(ImageScaleMode mode) {
scaleMode_ = mode; scaleMode_ = mode;
if (useImageBackground_ && scaleMode_ == ImageScaleMode::Original && if (useImageBackground_ && scaleMode_ == ImageScaleMode::Original &&
@ -271,9 +318,7 @@ void Button::setBackgroundImageScaleMode(ImageScaleMode mode) {
} }
} }
void Button::setCustomSize(const Vec2 &size) { void Button::setCustomSize(const Vec2 &size) { setSize(size.x, size.y); }
setSize(size.x, size.y);
}
void Button::setCustomSize(float width, float height) { void Button::setCustomSize(float width, float height) {
setSize(width, height); setSize(width, height);
@ -326,15 +371,50 @@ void Button::drawBackgroundImage(RenderBackend &renderer, const Rect &rect) {
Texture *texture = nullptr; Texture *texture = nullptr;
Rect srcRect; Rect srcRect;
if (pressed_ && imgPressed_) { // 如果使用状态图片,根据 isOn_ 状态选择对应的图片
texture = imgPressed_.get(); if (useStateImages_) {
srcRect = useTextureRect_ ? imgPressedRect_ : Rect(0, 0, static_cast<float>(imgPressed_->getWidth()), static_cast<float>(imgPressed_->getHeight())); if (isOn_) {
} else if (hovered_ && imgHover_) { if (pressed_ && imgOnPressed_) {
texture = imgHover_.get(); texture = imgOnPressed_.get();
srcRect = useTextureRect_ ? imgHoverRect_ : Rect(0, 0, static_cast<float>(imgHover_->getWidth()), static_cast<float>(imgHover_->getHeight())); } else if (hovered_ && imgOnHover_) {
} else if (imgNormal_) { texture = imgOnHover_.get();
texture = imgNormal_.get(); } else {
srcRect = useTextureRect_ ? imgNormalRect_ : Rect(0, 0, static_cast<float>(imgNormal_->getWidth()), static_cast<float>(imgNormal_->getHeight())); texture = imgOnNormal_.get();
}
} else {
if (pressed_ && imgOffPressed_) {
texture = imgOffPressed_.get();
} else if (hovered_ && imgOffHover_) {
texture = imgOffHover_.get();
} else {
texture = imgOffNormal_.get();
}
}
if (texture) {
srcRect = Rect(0, 0, static_cast<float>(texture->getWidth()),
static_cast<float>(texture->getHeight()));
}
} else {
// 使用普通图片背景
if (pressed_ && imgPressed_) {
texture = imgPressed_.get();
srcRect = useTextureRect_
? imgPressedRect_
: Rect(0, 0, static_cast<float>(imgPressed_->getWidth()),
static_cast<float>(imgPressed_->getHeight()));
} else if (hovered_ && imgHover_) {
texture = imgHover_.get();
srcRect = useTextureRect_
? imgHoverRect_
: Rect(0, 0, static_cast<float>(imgHover_->getWidth()),
static_cast<float>(imgHover_->getHeight()));
} else if (imgNormal_) {
texture = imgNormal_.get();
srcRect = useTextureRect_
? imgNormalRect_
: Rect(0, 0, static_cast<float>(imgNormal_->getWidth()),
static_cast<float>(imgNormal_->getHeight()));
}
} }
if (!texture) if (!texture)
@ -349,8 +429,8 @@ void Button::drawBackgroundImage(RenderBackend &renderer, const Rect &rect) {
Rect destRect(drawPos.x, drawPos.y, drawSize.x, drawSize.y); Rect destRect(drawPos.x, drawPos.y, drawSize.x, drawSize.y);
renderer.drawSprite(*texture, destRect, srcRect, renderer.drawSprite(*texture, destRect, srcRect, Colors::White, 0.0f,
Colors::White, 0.0f, Vec2::Zero()); Vec2::Zero());
} }
void Button::drawRoundedRect(RenderBackend &renderer, const Rect &rect, void Button::drawRoundedRect(RenderBackend &renderer, const Rect &rect,
@ -371,9 +451,11 @@ void Button::drawRoundedRect(RenderBackend &renderer, const Rect &rect,
float r = radius; float r = radius;
renderer.drawLine(Vec2(x + r, y), Vec2(x + w - r, y), color, borderWidth_); renderer.drawLine(Vec2(x + r, y), Vec2(x + w - r, y), color, borderWidth_);
renderer.drawLine(Vec2(x + r, y + h), Vec2(x + w - r, y + h), color, borderWidth_); renderer.drawLine(Vec2(x + r, y + h), Vec2(x + w - r, y + h), color,
borderWidth_);
renderer.drawLine(Vec2(x, y + r), Vec2(x, y + h - r), color, borderWidth_); renderer.drawLine(Vec2(x, y + r), Vec2(x, y + h - r), color, borderWidth_);
renderer.drawLine(Vec2(x + w, y + r), Vec2(x + w, y + h - r), color, borderWidth_); renderer.drawLine(Vec2(x + w, y + r), Vec2(x + w, y + h - r), color,
borderWidth_);
for (int i = 0; i < segments; i++) { for (int i = 0; i < segments; i++) {
float angle1 = 3.14159f * 0.5f * (float)i / segments + 3.14159f; float angle1 = 3.14159f * 0.5f * (float)i / segments + 3.14159f;
@ -384,7 +466,8 @@ void Button::drawRoundedRect(RenderBackend &renderer, const Rect &rect,
} }
for (int i = 0; i < segments; i++) { for (int i = 0; i < segments; i++) {
float angle1 = 3.14159f * 0.5f * (float)i / segments + 3.14159f * 1.5f; float angle1 = 3.14159f * 0.5f * (float)i / segments + 3.14159f * 1.5f;
float angle2 = 3.14159f * 0.5f * (float)(i + 1) / segments + 3.14159f * 1.5f; float angle2 =
3.14159f * 0.5f * (float)(i + 1) / segments + 3.14159f * 1.5f;
Vec2 p1(x + w - r + r * cosf(angle1), y + r + r * sinf(angle1)); Vec2 p1(x + w - r + r * cosf(angle1), y + r + r * sinf(angle1));
Vec2 p2(x + w - r + r * cosf(angle2), y + r + r * sinf(angle2)); Vec2 p2(x + w - r + r * cosf(angle2), y + r + r * sinf(angle2));
renderer.drawLine(p1, p2, color, borderWidth_); renderer.drawLine(p1, p2, color, borderWidth_);
@ -398,7 +481,8 @@ void Button::drawRoundedRect(RenderBackend &renderer, const Rect &rect,
} }
for (int i = 0; i < segments; i++) { for (int i = 0; i < segments; i++) {
float angle1 = 3.14159f * 0.5f * (float)i / segments + 3.14159f * 0.5f; float angle1 = 3.14159f * 0.5f * (float)i / segments + 3.14159f * 0.5f;
float angle2 = 3.14159f * 0.5f * (float)(i + 1) / segments + 3.14159f * 0.5f; float angle2 =
3.14159f * 0.5f * (float)(i + 1) / segments + 3.14159f * 0.5f;
Vec2 p1(x + r + r * cosf(angle1), y + h - r + r * sinf(angle1)); Vec2 p1(x + r + r * cosf(angle1), y + h - r + r * sinf(angle1));
Vec2 p2(x + r + r * cosf(angle2), y + h - r + r * sinf(angle2)); Vec2 p2(x + r + r * cosf(angle2), y + h - r + r * sinf(angle2));
renderer.drawLine(p1, p2, color, borderWidth_); renderer.drawLine(p1, p2, color, borderWidth_);
@ -513,169 +597,40 @@ void Button::onDrawWidget(RenderBackend &renderer) {
renderer.beginSpriteBatch(); renderer.beginSpriteBatch();
} }
if (font_ && !text_.empty()) { if (font_) {
Vec2 textSize = font_->measureText(text_); // 确定要显示的文字
Vec2 textPos(rect.center().x - textSize.x * 0.5f,
rect.center().y - textSize.y * 0.5f);
float minX = rect.left() + padding_.x;
float minY = rect.top() + padding_.y;
float maxX = rect.right() - padding_.x - textSize.x;
float maxY = rect.bottom() - padding_.y - textSize.y;
textPos.x = std::max(minX, std::min(textPos.x, maxX));
textPos.y = std::max(minY, std::min(textPos.y, maxY));
Color finalTextColor = textColor_;
finalTextColor.a = 1.0f;
renderer.drawText(*font_, text_, textPos, finalTextColor);
}
}
// ============================================================================
// ToggleImageButton 实现
// ============================================================================
ToggleImageButton::ToggleImageButton() {
setOnClick([this]() { toggle(); });
}
Ptr<ToggleImageButton> ToggleImageButton::create() {
return makePtr<ToggleImageButton>();
}
void ToggleImageButton::setStateImages(Ptr<Texture> stateOffNormal,
Ptr<Texture> stateOnNormal,
Ptr<Texture> stateOffHover,
Ptr<Texture> stateOnHover,
Ptr<Texture> stateOffPressed,
Ptr<Texture> stateOnPressed) {
imgOffNormal_ = stateOffNormal;
imgOnNormal_ = stateOnNormal;
imgOffHover_ = stateOffHover ? stateOffHover : stateOffNormal;
imgOnHover_ = stateOnHover ? stateOnHover : stateOnNormal;
imgOffPressed_ = stateOffPressed ? stateOffPressed : stateOffNormal;
imgOnPressed_ = stateOnPressed ? stateOnPressed : stateOnNormal;
if (imgOffNormal_) {
setSize(static_cast<float>(imgOffNormal_->getWidth()),
static_cast<float>(imgOffNormal_->getHeight()));
}
}
void ToggleImageButton::setOn(bool on) {
if (isOn_ != on) {
isOn_ = on;
if (onStateChange_) {
onStateChange_(isOn_);
}
}
}
void ToggleImageButton::toggle() {
setOn(!isOn_);
}
void ToggleImageButton::setOnStateChange(Function<void(bool)> callback) {
onStateChange_ = std::move(callback);
}
void ToggleImageButton::setStateText(const std::string &textOff,
const std::string &textOn) {
textOff_ = textOff;
textOn_ = textOn;
useStateText_ = true;
}
void ToggleImageButton::setStateTextColor(const Color &colorOff,
const Color &colorOn) {
textColorOff_ = colorOff;
textColorOn_ = colorOn;
useStateTextColor_ = true;
}
void ToggleImageButton::onDrawWidget(RenderBackend &renderer) {
Rect rect = getBoundingBox();
if (rect.empty()) {
return;
}
Ptr<Texture> texture = nullptr;
if (isOn_) {
if (isPressed() && imgOnPressed_) {
texture = imgOnPressed_;
} else if (isHovered() && imgOnHover_) {
texture = imgOnHover_;
} else {
texture = imgOnNormal_;
}
} else {
if (isPressed() && imgOffPressed_) {
texture = imgOffPressed_;
} else if (isHovered() && imgOffHover_) {
texture = imgOffHover_;
} else {
texture = imgOffNormal_;
}
}
if (texture) {
Vec2 imageSize(static_cast<float>(texture->getWidth()),
static_cast<float>(texture->getHeight()));
Vec2 buttonSize(rect.size.width, rect.size.height);
Vec2 drawSize = imageSize;
Vec2 drawPos(rect.origin.x + (buttonSize.x - drawSize.x) * 0.5f,
rect.origin.y + (buttonSize.y - drawSize.y) * 0.5f);
Rect destRect(drawPos.x, drawPos.y, drawSize.x, drawSize.y);
renderer.drawSprite(*texture, destRect,
Rect(0, 0, imageSize.x, imageSize.y), Colors::White,
0.0f, Vec2::Zero());
}
renderer.endSpriteBatch();
float borderWidth = 1.0f;
Color borderColor =
isOn_ ? Color(0.0f, 1.0f, 0.0f, 0.8f) : Color(0.6f, 0.6f, 0.6f, 1.0f);
if (borderWidth > 0.0f) {
if (isRoundedCornersEnabled()) {
drawRoundedRect(renderer, rect, borderColor, getCornerRadius());
} else {
renderer.drawRect(rect, borderColor, borderWidth);
}
}
renderer.beginSpriteBatch();
auto font = getFont();
if (font) {
std::string textToDraw; std::string textToDraw;
if (useStateText_) { if (useStateText_) {
textToDraw = isOn_ ? textOn_ : textOff_; textToDraw = isOn_ ? textOn_ : textOff_;
} else { } else {
textToDraw = getText(); textToDraw = text_;
} }
// 确定文字颜色
Color colorToUse; Color colorToUse;
if (useStateTextColor_) { if (useStateTextColor_) {
colorToUse = isOn_ ? textColorOn_ : textColorOff_; colorToUse = isOn_ ? textColorOn_ : textColorOff_;
} else { } else {
colorToUse = getTextColor(); colorToUse = textColor_;
} }
if (!textToDraw.empty()) { if (!textToDraw.empty()) {
Vec2 textSize = font->measureText(textToDraw); Vec2 textSize = font_->measureText(textToDraw);
Vec2 textPos(rect.center().x - textSize.x * 0.5f, Vec2 textPos(rect.center().x - textSize.x * 0.5f,
rect.center().y - textSize.y * 0.5f); rect.center().y - textSize.y * 0.5f);
float minX = rect.left() + padding_.x;
float minY = rect.top() + padding_.y;
float maxX = rect.right() - padding_.x - textSize.x;
float maxY = rect.bottom() - padding_.y - textSize.y;
textPos.x = std::max(minX, std::min(textPos.x, maxX));
textPos.y = std::max(minY, std::min(textPos.y, maxY));
colorToUse.a = 1.0f; colorToUse.a = 1.0f;
renderer.drawText(*font, textToDraw, textPos, colorToUse); renderer.drawText(*font_, textToDraw, textPos, colorToUse);
} }
} }
} }

View File

@ -4,7 +4,7 @@ Extra2D 的场景系统提供了游戏内容的分层管理和切换功能。本
## 完整示例 ## 完整示例
参考 `examples/flappy_bird/` 中的实现: 参考 `examples/flappy_bird/` `examples/push_box/` 中的实现:
- `BaseScene.h/cpp` - 基础场景类(视口适配) - `BaseScene.h/cpp` - 基础场景类(视口适配)
- `StartScene.h/cpp` - 开始菜单场景 - `StartScene.h/cpp` - 开始菜单场景
@ -193,6 +193,14 @@ void GameScene::onUpdate(float dt) {
4. **所有坐标使用逻辑分辨率** - 不依赖窗口实际大小 4. **所有坐标使用逻辑分辨率** - 不依赖窗口实际大小
5. **处理窗口大小变化** - 在 `onRender()` 中检测并更新 5. **处理窗口大小变化** - 在 `onRender()` 中检测并更新
### 注意事项
1. **相机设置必须正确** - 使用 `camera->setViewport(0.0f, GAME_WIDTH, GAME_HEIGHT, 0.0f)` 确保相机覆盖整个游戏区域
2. **渲染器视口与相机视口不同** - 渲染器视口控制实际渲染到屏幕的区域,相机视口控制世界坐标到屏幕坐标的映射
3. **窗口大小变化检测** - 在 `onRender()` 中检测窗口大小变化并重新计算视口,确保窗口调整时正确适配
4. **子类必须调用父类方法** - `onEnter()``onUpdate()` 中必须调用 `BaseScene::onEnter()``BaseScene::onUpdate()`
5. **UI元素坐标空间** - UI控件通常使用 `CoordinateSpace::Screen` 固定在屏幕上,不受视口适配影响
## 场景基础 ## 场景基础
### 创建场景 ### 创建场景
@ -230,23 +238,37 @@ public:
}; };
``` ```
### 场景切换 ### 场景切换新API
通过 `SceneManager` 进行场景切换:
```cpp ```cpp
// 进入场景(无过渡) auto& scenes = app.scenes();
app.enterScene(makePtr<GameScene>());
// 进入场景(有过渡效果) // 运行第一个场景
app.enterScene(makePtr<GameScene>(), TransitionType::Fade, 0.5f); scenes.runWithScene(makePtr<GameScene>());
// 替换当前场景 // 替换当前场景(无过渡)
app.scenes().replaceScene(makePtr<NewScene>()); scenes.replaceScene(makePtr<GameScene>());
// 替换当前场景(有过渡效果)
scenes.replaceScene(makePtr<GameScene>(), TransitionType::Fade, 0.5f);
// 推入场景(保留当前场景) // 推入场景(保留当前场景)
app.scenes().pushScene(makePtr<NewScene>()); scenes.pushScene(makePtr<NewScene>());
scenes.pushScene(makePtr<NewScene>(), TransitionType::SlideLeft, 0.5f);
// 弹出场景(返回上一个场景) // 弹出场景(返回上一个场景)
app.scenes().popScene(); scenes.popScene();
scenes.popScene(TransitionType::Fade, 0.5f);
// 弹出到根场景
scenes.popToRootScene();
scenes.popToRootScene(TransitionType::Fade, 0.5f);
// 弹出到指定场景
scenes.popToScene("SceneName");
scenes.popToScene("SceneName", TransitionType::Fade, 0.5f);
``` ```
### 过渡效果类型 ### 过渡效果类型
@ -258,11 +280,14 @@ enum class TransitionType {
SlideLeft, // 向左滑动 SlideLeft, // 向左滑动
SlideRight, // 向右滑动 SlideRight, // 向右滑动
SlideUp, // 向上滑动 SlideUp, // 向上滑动
SlideDown // 向下滑动 SlideDown, // 向下滑动
Scale, // 缩放过渡
Flip, // 翻转过渡
Box // 盒子过渡
}; };
``` ```
## 场景管理器 ### 场景管理器
通过 `app.scenes()` 访问场景管理器: 通过 `app.scenes()` 访问场景管理器:
@ -270,13 +295,25 @@ enum class TransitionType {
auto& scenes = app.scenes(); auto& scenes = app.scenes();
// 获取当前场景 // 获取当前场景
auto current = scenes.currentScene(); auto current = scenes.getCurrentScene();
// 获取上一个场景
auto previous = scenes.getPreviousScene();
// 获取根场景
auto root = scenes.getRootScene();
// 通过名称获取场景
auto scene = scenes.getSceneByName("SceneName");
// 获取场景栈深度 // 获取场景栈深度
size_t depth = scenes.stackDepth(); size_t count = scenes.getSceneCount();
// 检查是否正在过渡
bool transitioning = scenes.isTransitioning();
// 清空场景栈 // 清空场景栈
scenes.clearStack(); scenes.end();
``` ```
## 场景生命周期 ## 场景生命周期
@ -284,7 +321,7 @@ scenes.clearStack();
``` ```
创建场景 (makePtr<Scene>) 创建场景 (makePtr<Scene>)
进入场景 (enterScene) 进入场景 (runWithScene/replaceScene/pushScene)
onEnter() - 初始化资源 onEnter() - 初始化资源
@ -411,10 +448,12 @@ private:
} }
void executeMenuItem() { void executeMenuItem() {
auto& scenes = Application::instance().scenes();
switch (selectedIndex_) { switch (selectedIndex_) {
case 0: case 0:
Application::instance().scenes().replaceScene( scenes.replaceScene(makePtr<GameScene>(),
makePtr<GameScene>(), TransitionType::Fade, 0.25f); TransitionType::Fade, 0.25f);
break; break;
case 1: case 1:
// 打开设置 // 打开设置
@ -438,6 +477,8 @@ private:
2. **在 onExit 中清理资源** - 避免内存泄漏 2. **在 onExit 中清理资源** - 避免内存泄漏
3. **使用过渡效果** - 提升用户体验 3. **使用过渡效果** - 提升用户体验
4. **分离场景逻辑** - 每个场景负责自己的功能 4. **分离场景逻辑** - 每个场景负责自己的功能
5. **使用视口适配** - 确保游戏在不同分辨率下正确显示
6. **正确处理窗口大小变化** - 在 `onRender()` 中检测并更新视口
## 下一步 ## 下一步

View File

@ -109,7 +109,7 @@ button->setBorder(Colors::White, 2.0f);
button->setRoundedCornersEnabled(true); button->setRoundedCornersEnabled(true);
button->setCornerRadius(8.0f); button->setCornerRadius(8.0f);
// 图片背景 // 图片背景(普通按钮)
button->setBackgroundImage(normalTex, hoverTex, pressedTex); button->setBackgroundImage(normalTex, hoverTex, pressedTex);
button->setBackgroundImageScaleMode(ImageScaleMode::ScaleFit); button->setBackgroundImageScaleMode(ImageScaleMode::ScaleFit);
@ -117,6 +117,51 @@ button->setBackgroundImageScaleMode(ImageScaleMode::ScaleFit);
button->setHoverCursor(CursorShape::Hand); button->setHoverCursor(CursorShape::Hand);
``` ```
### 切换按钮模式Toggle Button
按钮支持切换模式,可以在 on/off 两种状态间切换:
```cpp
// 创建切换按钮
auto toggleBtn = Button::create();
toggleBtn->setToggleMode(true); // 启用切换模式
// 设置状态图片off状态图片, on状态图片
toggleBtn->setStateBackgroundImage(soundOffTex, soundOnTex);
// 或设置带悬停/按下效果的状态图片
toggleBtn->setStateBackgroundImage(
soundOffTex, soundOnTex, // 普通状态
soundOffHoverTex, soundOnHoverTex, // 悬停状态(可选)
soundOffPressedTex, soundOnPressedTex // 按下状态(可选)
);
// 设置状态文字
toggleBtn->setStateText("关闭", "开启");
// 设置状态文字颜色
toggleBtn->setStateTextColor(Colors::Red, Colors::Green);
// 设置初始状态
toggleBtn->setOn(true);
// 获取当前状态
bool isOn = toggleBtn->isOn();
// 手动切换状态
toggleBtn->toggle();
// 状态改变回调
toggleBtn->setOnStateChange([](bool isOn) {
E2D_LOG_INFO("切换按钮状态: {}", isOn ? "开启" : "关闭");
});
// 点击回调(切换模式也会触发此回调)
toggleBtn->setOnClick([]() {
E2D_LOG_INFO("按钮被点击");
});
```
### 图片缩放模式 ### 图片缩放模式
```cpp ```cpp
@ -245,6 +290,7 @@ void GameOverLayer::onUpdate(float dt) {
- **动画播放期间**:禁用按钮,防止用户过早操作 - **动画播放期间**:禁用按钮,防止用户过早操作
- **加载过程中**:禁用按钮,显示加载状态 - **加载过程中**:禁用按钮,显示加载状态
- **条件限制**:当条件不满足时禁用按钮(如未选择关卡) - **条件限制**:当条件不满足时禁用按钮(如未选择关卡)
- **切换按钮**:音效开关、设置开关等需要显示两种状态的按钮
## 文本Text ## 文本Text
@ -350,11 +396,11 @@ checkBox->setPosition(Vec2(100, 200));
checkBox->setChecked(true); checkBox->setChecked(true);
// 方式2带标签 // 方式2带标签
auto checkBox = CheckBox::create("启用音效"); checkBox = CheckBox::create("启用音效");
checkBox->setPosition(Vec2(100, 200)); checkBox->setPosition(Vec2(100, 200));
// 方式3链式调用 // 方式3链式调用
auto checkBox = CheckBox::create("启用音效") checkBox = CheckBox::create("启用音效")
->withPosition(100, 200) ->withPosition(100, 200)
->withFont(font) ->withFont(font)
->withTextColor(Colors::White); ->withTextColor(Colors::White);
@ -620,18 +666,24 @@ public:
title->setAlignment(Alignment::Center); title->setAlignment(Alignment::Center);
addChild(title); addChild(title);
// 音效开关 // 音效开关(使用切换按钮)
auto soundLabel = Label::create("音效", font_); auto soundLabel = Label::create("音效", font_);
soundLabel->setPosition(Vec2(200, 200)); soundLabel->setPosition(Vec2(200, 200));
addChild(soundLabel); addChild(soundLabel);
soundCheck_ = CheckBox::create(); auto soundOn = resources.loadTexture("assets/sound_on.png");
soundCheck_->setPosition(Vec2(350, 200)); auto soundOff = resources.loadTexture("assets/sound_off.png");
soundCheck_->setChecked(true);
soundCheck_->setOnStateChange([this](bool checked) { soundToggle_ = Button::create();
E2D_LOG_INFO("音效: {}", checked ? "开启" : "关闭"); soundToggle_->setPosition(Vec2(350, 200));
soundToggle_->setToggleMode(true);
soundToggle_->setStateBackgroundImage(soundOff, soundOn);
soundToggle_->setOn(true);
soundToggle_->setOnStateChange([](bool isOn) {
E2D_LOG_INFO("音效: {}", isOn ? "开启" : "关闭");
AudioManager::instance().setEnabled(isOn);
}); });
addChild(soundCheck_); addChild(soundToggle_);
// 音量滑块 // 音量滑块
auto volumeLabel = Label::create("音量", font_); auto volumeLabel = Label::create("音量", font_);
@ -685,7 +737,7 @@ public:
private: private:
Ptr<FontAtlas> font_; Ptr<FontAtlas> font_;
Ptr<CheckBox> soundCheck_; Ptr<Button> soundToggle_;
Ptr<Slider> volumeSlider_; Ptr<Slider> volumeSlider_;
}; };
``` ```
@ -697,6 +749,7 @@ private:
3. **设置合适的锚点** - 使用锚点0.5, 0.5)让控件中心对齐,方便布局 3. **设置合适的锚点** - 使用锚点0.5, 0.5)让控件中心对齐,方便布局
4. **复用字体资源** - 避免重复加载相同字体 4. **复用字体资源** - 避免重复加载相同字体
5. **使用回调函数** - 使用 `setOnClick`、`setOnValueChange` 等回调响应用户操作 5. **使用回调函数** - 使用 `setOnClick`、`setOnValueChange` 等回调响应用户操作
6. **使用切换按钮** - 对于需要显示两种状态的按钮(如开关),使用 `setToggleMode(true)`
## 下一步 ## 下一步

View File

@ -3,6 +3,7 @@
// ============================================================================ // ============================================================================
#include "BaseScene.h" #include "BaseScene.h"
#include <extra2d/scene/transition_scene.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
namespace flappybird { namespace flappybird {
@ -66,10 +67,6 @@ void BaseScene::onRender(extra2d::RenderBackend &renderer) {
} }
// 设置视口为居中区域 // 设置视口为居中区域
E2D_LOG_INFO(
"BaseScene::onRender - setting viewport: x={}, y={}, width={}, height={}",
static_cast<int>(viewportOffsetX_), static_cast<int>(viewportOffsetY_),
static_cast<int>(scaledGameWidth_), static_cast<int>(scaledGameHeight_));
renderer.setViewport( renderer.setViewport(
static_cast<int>(viewportOffsetX_), static_cast<int>(viewportOffsetY_), static_cast<int>(viewportOffsetX_), static_cast<int>(viewportOffsetY_),
static_cast<int>(scaledGameWidth_), static_cast<int>(scaledGameHeight_)); static_cast<int>(scaledGameWidth_), static_cast<int>(scaledGameHeight_));
@ -78,4 +75,53 @@ void BaseScene::onRender(extra2d::RenderBackend &renderer) {
extra2d::Scene::onRender(renderer); extra2d::Scene::onRender(renderer);
} }
void BaseScene::renderContent(extra2d::RenderBackend &renderer) {
// 如果视口参数未初始化onEnter 还没被调用),先初始化
if (scaledGameWidth_ <= 0.0f || scaledGameHeight_ <= 0.0f) {
updateViewport();
}
// 检查窗口大小是否改变
auto &app = extra2d::Application::instance();
float currentWindowWidth = static_cast<float>(app.window().getWidth());
float currentWindowHeight = static_cast<float>(app.window().getHeight());
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 parent = getParent()) {
if (dynamic_cast<extra2d::TransitionScene *>(parent.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<int>(viewportOffsetX_),
static_cast<int>(viewportOffsetY_),
static_cast<int>(scaledGameWidth_),
static_cast<int>(scaledGameHeight_));
extra2d::Scene::renderContent(renderer);
}
}
} // namespace flappybird } // namespace flappybird

View File

@ -35,6 +35,12 @@ public:
*/ */
void onRender(extra2d::RenderBackend &renderer) override; void onRender(extra2d::RenderBackend &renderer) override;
/**
* @brief
* @param renderer
*/
void renderContent(extra2d::RenderBackend &renderer) override;
protected: protected:
/** /**
* @brief 使 * @brief 使

View File

@ -43,7 +43,7 @@ void SplashScene::onUpdate(float dt) {
void SplashScene::gotoStartScene() { void SplashScene::gotoStartScene() {
auto &app = extra2d::Application::instance(); auto &app = extra2d::Application::instance();
app.scenes().replaceScene(extra2d::makePtr<StartScene>(), app.scenes().replaceScene(extra2d::makePtr<StartScene>(),
extra2d::TransitionType::Fade, 0.5f); extra2d::TransitionType::Fade, 2.0f);
} }
} // namespace flappybird } // namespace flappybird

View File

@ -0,0 +1,138 @@
// ============================================================================
// BaseScene.cpp - Push Box 基础场景实现
// ============================================================================
#include "BaseScene.h"
#include <extra2d/scene/transition_scene.h>
#include <extra2d/utils/logger.h>
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<float>(app.window().getWidth());
float windowHeight = static_cast<float>(app.window().getHeight());
// 计算游戏内容在窗口中的居中位置
// 保持游戏原始宽高比,进行"黑边"适配
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<extra2d::Camera>();
// 设置正交投影,覆盖整个游戏逻辑区域
// 注意对于2D游戏Y轴向下增长所以bottom > top
camera->setViewport(0.0f, GAME_WIDTH, GAME_HEIGHT, 0.0f);
setCamera(camera);
}
/**
* @brief
* @param renderer
*/
void BaseScene::onRender(extra2d::RenderBackend &renderer) {
// 检查窗口大小是否改变,如果改变则更新视口
auto &app = extra2d::Application::instance();
float currentWindowWidth = static_cast<float>(app.window().getWidth());
float currentWindowHeight = static_cast<float>(app.window().getHeight());
// 如果窗口大小改变,重新计算视口
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<int>(viewportOffsetX_), static_cast<int>(viewportOffsetY_),
static_cast<int>(scaledGameWidth_), static_cast<int>(scaledGameHeight_));
// 调用父类的 onRender 进行实际渲染
extra2d::Scene::onRender(renderer);
}
/**
* @brief
* @param renderer
*/
void BaseScene::renderContent(extra2d::RenderBackend &renderer) {
// 如果视口参数未初始化onEnter 还没被调用),先初始化
if (scaledGameWidth_ <= 0.0f || scaledGameHeight_ <= 0.0f) {
updateViewport();
}
// 检查窗口大小是否改变
auto &app = extra2d::Application::instance();
float currentWindowWidth = static_cast<float>(app.window().getWidth());
float currentWindowHeight = static_cast<float>(app.window().getHeight());
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 parent = getParent()) {
if (dynamic_cast<extra2d::TransitionScene *>(parent.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<int>(viewportOffsetX_),
static_cast<int>(viewportOffsetY_),
static_cast<int>(scaledGameWidth_),
static_cast<int>(scaledGameHeight_));
extra2d::Scene::renderContent(renderer);
}
}
} // namespace pushbox

View File

@ -0,0 +1,57 @@
// ============================================================================
// BaseScene.h - Push Box 基础场景类
// 描述: 提供统一的居中视口适配功能,所有游戏场景都应继承此类
// ============================================================================
#pragma once
#include <extra2d/extra2d.h>
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::RenderBackend &renderer) override;
/**
* @brief
* @param renderer
*/
void renderContent(extra2d::RenderBackend &renderer) override;
protected:
/**
* @brief 使
*/
void updateViewport();
// 视口适配参数(用于在窗口中居中显示游戏内容)
float scaledGameWidth_ = 0.0f; // 缩放后的游戏宽度
float scaledGameHeight_ = 0.0f; // 缩放后的游戏高度
float viewportOffsetX_ = 0.0f; // 视口水平偏移
float viewportOffsetY_ = 0.0f; // 视口垂直偏移
};
} // namespace pushbox

View File

@ -1,386 +1,418 @@
// ============================================================================
// PlayScene.cpp - Push Box 游戏场景实现
// ============================================================================
#include "PlayScene.h" #include "PlayScene.h"
#include "audio_manager.h"
#include "storage.h"
#include "StartScene.h" #include "StartScene.h"
#include "SuccessScene.h" #include "SuccessScene.h"
#include "audio_manager.h"
#include "storage.h"
#include <extra2d/extra2d.h> #include <extra2d/extra2d.h>
namespace pushbox { namespace pushbox {
/**
* @brief
* @param size
*/
static extra2d::Ptr<extra2d::FontAtlas> loadFont(int size) { static extra2d::Ptr<extra2d::FontAtlas> loadFont(int size) {
auto& resources = extra2d::Application::instance().resources(); auto &resources = extra2d::Application::instance().resources();
auto font = resources.loadFont("assets/font.ttf", size); auto font = resources.loadFont("assets/font.ttf", size);
return font; return font;
} }
PlayScene::PlayScene(int level) { PlayScene::PlayScene(int level) : BaseScene() {
setBackgroundColor(extra2d::Colors::Black); auto &app = extra2d::Application::instance();
auto &resources = app.resources();
auto& app = extra2d::Application::instance(); E2D_LOG_INFO("PlayScene: Loading textures...");
auto& config = app.getConfig();
setViewportSize(static_cast<float>(config.width), static_cast<float>(config.height));
auto& resources = app.resources(); 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");
E2D_LOG_INFO("PlayScene: Loading textures..."); 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");
texWall_ = resources.loadTexture("assets/images/wall.gif"); texManPush_[1] = resources.loadTexture("assets/images/player/manhandup.gif");
texPoint_ = resources.loadTexture("assets/images/point.gif"); texManPush_[2] =
texFloor_ = resources.loadTexture("assets/images/floor.gif"); resources.loadTexture("assets/images/player/manhanddown.gif");
texBox_ = resources.loadTexture("assets/images/box.gif"); texManPush_[3] =
texBoxInPoint_ = resources.loadTexture("assets/images/boxinpoint.gif"); resources.loadTexture("assets/images/player/manhandleft.gif");
texManPush_[4] =
resources.loadTexture("assets/images/player/manhandright.gif");
texMan_[1] = resources.loadTexture("assets/images/player/manup.gif"); font28_ = loadFont(28);
texMan_[2] = resources.loadTexture("assets/images/player/mandown.gif"); font20_ = loadFont(20);
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"); float screenW = GAME_WIDTH;
texManPush_[3] = resources.loadTexture("assets/images/player/manhandleft.gif"); float screenH = GAME_HEIGHT;
texManPush_[4] = resources.loadTexture("assets/images/player/manhandright.gif");
font28_ = loadFont(28); // 计算游戏区域居中偏移
font20_ = loadFont(20); float offsetX = (screenW - GAME_WIDTH) / 2.0f;
float offsetY = (screenH - GAME_HEIGHT) / 2.0f;
// 获取窗口尺寸 // 音效开关按钮(使用 Button 的切换模式)
float screenW = static_cast<float>(app.getConfig().width); auto soundOn = resources.loadTexture("assets/images/soundon.png");
float screenH = static_cast<float>(app.getConfig().height); 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_);
}
// 计算游戏区域居中偏移(假设游戏区域是 640x480 levelText_ = extra2d::Text::create("", font28_);
float gameWidth = 640.0f; levelText_->setPosition(offsetX + 520.0f, offsetY + 30.0f);
float gameHeight = 480.0f; levelText_->setTextColor(extra2d::Colors::White);
float offsetX = (screenW - gameWidth) / 2.0f; addChild(levelText_);
float offsetY = (screenH - gameHeight) / 2.0f;
// 音效图标(左上角,与主界面一致) stepText_ = extra2d::Text::create("", font20_);
auto soundOn = resources.loadTexture("assets/images/soundon.png"); stepText_->setPosition(offsetX + 520.0f, offsetY + 100.0f);
auto soundOff = resources.loadTexture("assets/images/soundoff.png"); stepText_->setTextColor(extra2d::Colors::White);
if (soundOn && soundOff) { addChild(stepText_);
soundIcon_ = extra2d::Sprite::create(g_SoundOpen ? soundOn : soundOff);
soundIcon_->setPosition(offsetX + 50.0f, offsetY + 50.0f);
addChild(soundIcon_);
}
levelText_ = extra2d::Text::create("", font28_); bestText_ = extra2d::Text::create("", font20_);
levelText_->setPosition(offsetX + 520.0f, offsetY + 30.0f); bestText_->setPosition(offsetX + 520.0f, offsetY + 140.0f);
levelText_->setTextColor(extra2d::Colors::White); bestText_->setTextColor(extra2d::Colors::White);
addChild(levelText_); addChild(bestText_);
stepText_ = extra2d::Text::create("", font20_); // 创建菜单文本(使用颜色变化指示选中)
stepText_->setPosition(offsetX + 520.0f, offsetY + 100.0f); restartText_ = extra2d::Text::create("Y键重开", font20_);
stepText_->setTextColor(extra2d::Colors::White); restartText_->setPosition(offsetX + 520.0f, offsetY + 290.0f);
addChild(stepText_); addChild(restartText_);
bestText_ = extra2d::Text::create("", font20_); soundToggleText_ = extra2d::Text::create("X键切换音效", font20_);
bestText_->setPosition(offsetX + 520.0f, offsetY + 140.0f); soundToggleText_->setPosition(offsetX + 520.0f, offsetY + 330.0f);
bestText_->setTextColor(extra2d::Colors::White); addChild(soundToggleText_);
addChild(bestText_);
// 创建菜单文本(使用颜色变化指示选中) mapLayer_ = extra2d::makePtr<extra2d::Node>();
restartText_ = extra2d::Text::create("Y键重开", font20_); mapLayer_->setAnchor(0.0f, 0.0f);
restartText_->setPosition(offsetX + 520.0f, offsetY + 290.0f); mapLayer_->setPosition(0.0f, 0.0f);
addChild(restartText_); addChild(mapLayer_);
soundToggleText_ = extra2d::Text::create("X键切换音效", font20_); setLevel(level);
soundToggleText_->setPosition(offsetX + 520.0f, offsetY + 330.0f);
addChild(soundToggleText_);
mapLayer_ = extra2d::makePtr<extra2d::Node>();
mapLayer_->setAnchor(0.0f, 0.0f);
mapLayer_->setPosition(0.0f, 0.0f);
addChild(mapLayer_);
setLevel(level);
} }
void PlayScene::onEnter() { void PlayScene::onEnter() {
Scene::onEnter(); BaseScene::onEnter();
updateSoundIcon(); if (soundBtn_) {
updateMenuColors(); soundBtn_->setOn(g_SoundOpen);
}
updateMenuColors();
} }
/**
* @brief
*/
void PlayScene::updateMenuColors() { void PlayScene::updateMenuColors() {
// 选中的项用红色,未选中的用白色 // 选中的项用红色,未选中的用白色
if (restartText_) { if (restartText_) {
restartText_->setTextColor(menuIndex_ == 0 ? extra2d::Colors::Red : extra2d::Colors::White); restartText_->setTextColor(menuIndex_ == 0 ? extra2d::Colors::Red
} : extra2d::Colors::White);
if (soundToggleText_) { }
soundToggleText_->setTextColor(menuIndex_ == 1 ? extra2d::Colors::Red : extra2d::Colors::White); if (soundToggleText_) {
} soundToggleText_->setTextColor(menuIndex_ == 1 ? extra2d::Colors::Red
: extra2d::Colors::White);
}
} }
void PlayScene::onUpdate(float dt) { void PlayScene::onUpdate(float dt) {
Scene::onUpdate(dt); BaseScene::onUpdate(dt);
auto& app = extra2d::Application::instance(); auto &app = extra2d::Application::instance();
auto& input = app.input(); auto &input = app.input();
// B 键返回主菜单 // B 键返回主菜单
if (input.isButtonPressed(extra2d::GamepadButton::B)) { if (input.isButtonPressed(extra2d::GamepadButton::B)) {
app.scenes().replaceScene( app.scenes().replaceScene(extra2d::makePtr<StartScene>(),
extra2d::makePtr<StartScene>(), extra2d::TransitionType::Fade, 0.2f); extra2d::TransitionType::Fade, 0.5f);
return; 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;
}
// Y 键重开 // A 键执行选中的菜单项
if (input.isButtonPressed(extra2d::GamepadButton::Y)) { if (input.isButtonPressed(extra2d::GamepadButton::A)) {
setLevel(g_CurrentLevel); executeMenuItem();
return; return;
} }
// X键直接切换音效 // 方向键移动
if (input.isButtonPressed(extra2d::GamepadButton::X)) { if (input.isButtonPressed(extra2d::GamepadButton::DPadUp)) {
g_SoundOpen = !g_SoundOpen; move(0, -1, 1);
AudioManager::instance().setEnabled(g_SoundOpen);
updateSoundIcon();
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();
}
void PlayScene::executeMenuItem() {
switch (menuIndex_) {
case 0: // 重开
setLevel(g_CurrentLevel);
break;
case 1: // 切换音效
g_SoundOpen = !g_SoundOpen;
AudioManager::instance().setEnabled(g_SoundOpen);
updateSoundIcon();
break;
}
}
void PlayScene::updateSoundIcon() {
if (!soundIcon_) return;
auto& app = extra2d::Application::instance();
auto& resources = app.resources();
auto soundOn = resources.loadTexture("assets/images/soundon.png");
auto soundOff = resources.loadTexture("assets/images/soundoff.png");
if (soundOn && soundOff) {
soundIcon_->setTexture(g_SoundOpen ? soundOn : soundOff);
}
}
void PlayScene::flush() {
mapLayer_->removeAllChildren();
int tileW = texFloor_ ? texFloor_->getWidth() : 32;
int tileH = texFloor_ ? texFloor_->getHeight() : 32;
// 获取窗口尺寸,计算游戏区域居中偏移
auto& app = extra2d::Application::instance();
float screenW = static_cast<float>(app.getConfig().width);
float screenH = static_cast<float>(app.getConfig().height);
float gameWidth = 640.0f;
float gameHeight = 480.0f;
float baseOffsetX = (screenW - gameWidth) / 2.0f;
float baseOffsetY = (screenH - gameHeight) / 2.0f;
// 在 12x12 网格中居中地图
float mapOffsetX = static_cast<float>((12 - map_.width) / 2) * tileW;
float mapOffsetY = static_cast<float>((12 - map_.height) / 2) * 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<extra2d::Texture> 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<float>(i * tileW),
offsetY + static_cast<float>(j * tileH));
mapLayer_->addChild(sprite);
}
}
}
void PlayScene::setLevel(int level) {
g_CurrentLevel = level;
saveCurrentLevel(g_CurrentLevel);
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(); 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();
} }
void PlayScene::setStep(int step) { /**
step_ = step; * @brief
if (stepText_) { */
stepText_->setText("当前" + std::to_string(step) + ""); 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;
}
} }
void PlayScene::move(int dx, int dy, int direct) { /**
int targetX = dx + map_.roleX; * @brief
int targetY = dy + map_.roleY; */
g_Direct = direct; void PlayScene::flush() {
mapLayer_->removeAllChildren();
if (targetX < 0 || targetX >= map_.width || targetY < 0 || targetY >= map_.height) { int tileW = texFloor_ ? texFloor_->getWidth() : 32;
return; int tileH = texFloor_ ? texFloor_->getHeight() : 32;
// 使用游戏逻辑分辨率
float gameWidth = GAME_WIDTH;
float gameHeight = GAME_HEIGHT;
float baseOffsetX = 0.0f;
float baseOffsetY = 0.0f;
// 在 12x12 网格中居中地图
float mapOffsetX = static_cast<float>((12.0f - map_.width) / 2.0f) * tileW;
float mapOffsetY = static_cast<float>((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<extra2d::Texture> 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<float>(i * tileW),
offsetY + static_cast<float>(j * tileH));
mapLayer_->addChild(sprite);
} }
}
}
if (map_.value[targetY][targetX].type == TYPE::Wall) { /**
return; * @brief
} * @param level
*/
void PlayScene::setLevel(int level) {
g_CurrentLevel = level;
saveCurrentLevel(g_CurrentLevel);
if (map_.value[targetY][targetX].type == TYPE::Ground) { if (levelText_) {
g_Pushing = false; levelText_->setText("" + std::to_string(level) + "");
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; setStep(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) { int bestStep = loadBestStep(level, 0);
return; if (bestText_) {
} if (bestStep != 0) {
bestText_->setText("最佳" + std::to_string(bestStep) + "");
if (map_.value[boxY][boxX].type == TYPE::Wall || map_.value[boxY][boxX].type == TYPE::Box) {
return;
}
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 { } else {
return; bestText_->setText("");
} }
}
map_.roleX = targetX; // 深拷贝地图数据
map_.roleY = targetY; Map &sourceMap = g_Maps[level - 1];
setStep(step_ + 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;
}
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;
}
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;
}
map_.roleX = targetX;
map_.roleY = targetY;
setStep(step_ + 1);
}
/**
* @brief
*/
void PlayScene::gameOver() { void PlayScene::gameOver() {
int bestStep = loadBestStep(g_CurrentLevel, 0); int bestStep = loadBestStep(g_CurrentLevel, 0);
if (bestStep == 0 || step_ < bestStep) { if (bestStep == 0 || step_ < bestStep) {
saveBestStep(g_CurrentLevel, step_); saveBestStep(g_CurrentLevel, step_);
} }
if (g_CurrentLevel == MAX_LEVEL) { if (g_CurrentLevel == MAX_LEVEL) {
extra2d::Application::instance().scenes().pushScene(extra2d::makePtr<SuccessScene>(), extra2d::Application::instance().scenes().pushScene(
extra2d::TransitionType::Fade, 0.25f); extra2d::makePtr<SuccessScene>(), extra2d::TransitionType::Fade, 0.5f);
return; return;
} }
setLevel(g_CurrentLevel + 1); setLevel(g_CurrentLevel + 1);
} }
} // namespace pushbox } // namespace pushbox

View File

@ -1,25 +1,76 @@
// ============================================================================
// PlayScene.h - Push Box 游戏场景
// ============================================================================
#pragma once #pragma once
#include "BaseScene.h"
#include "data.h" #include "data.h"
#include <extra2d/extra2d.h> #include <extra2d/extra2d.h>
namespace pushbox { namespace pushbox {
class PlayScene : public extra2d::Scene { /**
* @brief Push Box
*/
class PlayScene : public BaseScene {
public: public:
/**
* @brief
* @param level
*/
explicit PlayScene(int level); explicit PlayScene(int level);
/**
* @brief
*/
void onEnter() override; void onEnter() override;
/**
* @brief
* @param dt
*/
void onUpdate(float dt) override; void onUpdate(float dt) override;
private: private:
/**
* @brief
*/
void updateMenuColors(); void updateMenuColors();
/**
* @brief
*/
void executeMenuItem(); void executeMenuItem();
void updateSoundIcon();
/**
* @brief
*/
void flush(); void flush();
/**
* @brief
* @param level
*/
void setLevel(int level); void setLevel(int level);
/**
* @brief
* @param step
*/
void setStep(int 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); void move(int dx, int dy, int direct);
/**
* @brief
*/
void gameOver(); void gameOver();
int step_ = 0; int step_ = 0;
@ -36,7 +87,7 @@ private:
extra2d::Ptr<extra2d::Text> soundToggleText_; extra2d::Ptr<extra2d::Text> soundToggleText_;
extra2d::Ptr<extra2d::Node> mapLayer_; extra2d::Ptr<extra2d::Node> mapLayer_;
extra2d::Ptr<extra2d::Sprite> soundIcon_; extra2d::Ptr<extra2d::Button> soundBtn_;
extra2d::Ptr<extra2d::Texture> texWall_; extra2d::Ptr<extra2d::Texture> texWall_;
extra2d::Ptr<extra2d::Texture> texPoint_; extra2d::Ptr<extra2d::Texture> texPoint_;

View File

@ -1,210 +1,227 @@
// ============================================================================
// StartScene.cpp - Push Box 开始场景实现
// ============================================================================
#include "StartScene.h" #include "StartScene.h"
#include "PlayScene.h"
#include "audio_manager.h" #include "audio_manager.h"
#include "data.h" #include "data.h"
#include "PlayScene.h"
#include <extra2d/extra2d.h> #include <extra2d/extra2d.h>
namespace pushbox { namespace pushbox {
StartScene::StartScene() { StartScene::StartScene() : BaseScene() {
auto& app = extra2d::Application::instance(); // BaseScene 已处理视口设置
auto& config = app.getConfig();
setViewportSize(static_cast<float>(config.width), static_cast<float>(config.height));
} }
/**
* @brief
*/
static extra2d::Ptr<extra2d::FontAtlas> loadMenuFont() { static extra2d::Ptr<extra2d::FontAtlas> loadMenuFont() {
auto& resources = extra2d::Application::instance().resources(); auto &resources = extra2d::Application::instance().resources();
auto font = resources.loadFont("assets/font.ttf", 28,true); auto font = resources.loadFont("assets/font.ttf", 28, true);
return font; return font;
} }
void StartScene::onEnter() { void StartScene::onEnter() {
Scene::onEnter(); BaseScene::onEnter();
auto& app = extra2d::Application::instance(); auto &app = extra2d::Application::instance();
auto& resources = app.resources(); auto &resources = app.resources();
setBackgroundColor(extra2d::Colors::Black);
if (getChildren().empty()) { if (getChildren().empty()) {
// 使用游戏逻辑分辨率
float screenW = GAME_WIDTH;
float screenH = GAME_HEIGHT;
float screenW = static_cast<float>(app.getConfig().width); auto bgTex = resources.loadTexture("assets/images/start.jpg");
float screenH = static_cast<float>(app.getConfig().height); if (bgTex) {
auto background = extra2d::Sprite::create(bgTex);
float bgWidth = static_cast<float>(bgTex->getWidth());
float bgHeight = static_cast<float>(bgTex->getHeight());
float offsetX = (screenW - bgWidth) / 2.0f;
float offsetY = (screenH - bgHeight) / 2.0f;
auto bgTex = resources.loadTexture("assets/images/start.jpg"); background->setAnchor(0.0f, 0.0f);
if (bgTex) { background->setPosition(offsetX, offsetY);
auto background = extra2d::Sprite::create(bgTex); addChild(background);
float bgWidth = static_cast<float>(bgTex->getWidth());
float bgHeight = static_cast<float>(bgTex->getHeight());
float offsetX = (screenW - bgWidth) / 2.0f;
float offsetY = (screenH - bgHeight) / 2.0f;
background->setAnchor(0.0f, 0.0f); float centerX = screenW / 2.0f;
background->setPosition(offsetX, offsetY);
addChild(background);
float centerX = screenW / 2.0f; font_ = loadMenuFont();
if (!font_) {
return;
}
font_ = loadMenuFont(); // 创建菜单按钮(使用 Button 实现文本居中)
if (!font_) { startBtn_ = extra2d::Button::create();
return; 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_);
// 创建菜单按钮(使用 Button 实现文本居中) resumeBtn_ = extra2d::Button::create();
// 设置按钮锚点为中心点,位置设为屏幕中心,实现真正的居中 resumeBtn_->setFont(font_);
startBtn_ = extra2d::Button::create(); resumeBtn_->setText("继续关卡");
startBtn_->setFont(font_); resumeBtn_->setTextColor(extra2d::Colors::Black);
startBtn_->setText("新游戏"); resumeBtn_->setBackgroundColor(extra2d::Colors::Transparent,
startBtn_->setTextColor(extra2d::Colors::Black); extra2d::Colors::Transparent,
startBtn_->setBackgroundColor(extra2d::Colors::Transparent, extra2d::Colors::Transparent);
extra2d::Colors::Transparent, resumeBtn_->setBorder(extra2d::Colors::Transparent, 0.0f);
extra2d::Colors::Transparent); resumeBtn_->setPadding(extra2d::Vec2(0.0f, 0.0f));
startBtn_->setBorder(extra2d::Colors::Transparent, 0.0f); resumeBtn_->setCustomSize(200.0f, 40.0f);
startBtn_->setPadding(extra2d::Vec2(0.0f, 0.0f)); resumeBtn_->setAnchor(0.5f, 0.5f);
startBtn_->setCustomSize(200.0f, 40.0f); resumeBtn_->setPosition(centerX, offsetY + 300.0f);
startBtn_->setAnchor(0.5f, 0.5f); addChild(resumeBtn_);
startBtn_->setPosition(centerX, offsetY + 260.0f);
addChild(startBtn_);
resumeBtn_ = extra2d::Button::create(); exitBtn_ = extra2d::Button::create();
resumeBtn_->setFont(font_); exitBtn_->setFont(font_);
resumeBtn_->setText("继续关卡"); exitBtn_->setText("退出");
resumeBtn_->setTextColor(extra2d::Colors::Black); exitBtn_->setTextColor(extra2d::Colors::Black);
resumeBtn_->setBackgroundColor(extra2d::Colors::Transparent, exitBtn_->setBackgroundColor(extra2d::Colors::Transparent,
extra2d::Colors::Transparent, extra2d::Colors::Transparent,
extra2d::Colors::Transparent); extra2d::Colors::Transparent);
resumeBtn_->setBorder(extra2d::Colors::Transparent, 0.0f); exitBtn_->setBorder(extra2d::Colors::Transparent, 0.0f);
resumeBtn_->setPadding(extra2d::Vec2(0.0f, 0.0f)); exitBtn_->setPadding(extra2d::Vec2(0.0f, 0.0f));
resumeBtn_->setCustomSize(200.0f, 40.0f); exitBtn_->setCustomSize(200.0f, 40.0f);
resumeBtn_->setAnchor(0.5f, 0.5f); exitBtn_->setAnchor(0.5f, 0.5f);
resumeBtn_->setPosition(centerX, offsetY + 300.0f); exitBtn_->setPosition(centerX, offsetY + 340.0f);
addChild(resumeBtn_); addChild(exitBtn_);
exitBtn_ = extra2d::Button::create(); // 音效开关按钮(使用 Button 的切换模式)
exitBtn_->setFont(font_); auto soundOn = resources.loadTexture("assets/images/soundon.png");
exitBtn_->setText("退出"); auto soundOff = resources.loadTexture("assets/images/soundoff.png");
exitBtn_->setTextColor(extra2d::Colors::Black); if (soundOn && soundOff) {
exitBtn_->setBackgroundColor(extra2d::Colors::Transparent, soundBtn_ = extra2d::Button::create();
extra2d::Colors::Transparent, soundBtn_->setToggleMode(true);
extra2d::Colors::Transparent); soundBtn_->setStateBackgroundImage(soundOff, soundOn);
exitBtn_->setBorder(extra2d::Colors::Transparent, 0.0f); soundBtn_->setOn(g_SoundOpen);
exitBtn_->setPadding(extra2d::Vec2(0.0f, 0.0f)); soundBtn_->setAnchor(0.0f, 0.0f);
exitBtn_->setCustomSize(200.0f, 40.0f); soundBtn_->setPosition(offsetX + 50.0f, offsetY + 50.0f);
exitBtn_->setAnchor(0.5f, 0.5f); soundBtn_->setOnStateChange([](bool isOn) {
exitBtn_->setPosition(centerX, offsetY + 340.0f); g_SoundOpen = isOn;
addChild(exitBtn_); AudioManager::instance().setEnabled(isOn);
});
// 音效开关图标(相对于背景图左上角) addChild(soundBtn_);
auto soundOn = resources.loadTexture("assets/images/soundon.png"); }
auto soundOff = resources.loadTexture("assets/images/soundoff.png");
if (soundOn && soundOff) {
soundIcon_ = extra2d::Sprite::create(g_SoundOpen ? soundOn : soundOff);
soundIcon_->setPosition(offsetX + 50.0f, offsetY + 50.0f);
addChild(soundIcon_);
}
}
} }
}
// 始终有3个菜单项 // 始终有3个菜单项
menuCount_ = 3; menuCount_ = 3;
updateMenuColors(); updateMenuColors();
} }
void StartScene::onUpdate(float dt) { void StartScene::onUpdate(float dt) {
Scene::onUpdate(dt); BaseScene::onUpdate(dt);
auto& app = extra2d::Application::instance(); auto &app = extra2d::Application::instance();
auto& input = app.input(); auto &input = app.input();
// 方向键上下切换选择 // 方向键上下切换选择
if (input.isButtonPressed(extra2d::GamepadButton::DPadUp)) { if (input.isButtonPressed(extra2d::GamepadButton::DPadUp)) {
selectedIndex_ = (selectedIndex_ - 1 + menuCount_) % menuCount_; selectedIndex_ = (selectedIndex_ - 1 + menuCount_) % menuCount_;
updateMenuColors(); updateMenuColors();
} else if (input.isButtonPressed(extra2d::GamepadButton::DPadDown)) { } else if (input.isButtonPressed(extra2d::GamepadButton::DPadDown)) {
selectedIndex_ = (selectedIndex_ + 1) % menuCount_; selectedIndex_ = (selectedIndex_ + 1) % menuCount_;
updateMenuColors(); updateMenuColors();
} }
// A键确认 // A键确认
if (input.isButtonPressed(extra2d::GamepadButton::A)) { if (input.isButtonPressed(extra2d::GamepadButton::A)) {
executeMenuItem(); executeMenuItem();
} }
// X键切换音效 // X键切换音效备用按钮也可点击切换
if (input.isButtonPressed(extra2d::GamepadButton::X)) { if (input.isButtonPressed(extra2d::GamepadButton::X)) {
g_SoundOpen = !g_SoundOpen; g_SoundOpen = !g_SoundOpen;
AudioManager::instance().setEnabled(g_SoundOpen); AudioManager::instance().setEnabled(g_SoundOpen);
updateSoundIcon(); if (soundBtn_) {
soundBtn_->setOn(g_SoundOpen);
} }
}
} }
/**
* @brief
*/
void StartScene::updateMenuColors() { void StartScene::updateMenuColors() {
// 根据选中状态更新按钮文本颜色 // 根据选中状态更新按钮文本颜色
// 选中的项用红色,未选中的用黑色,禁用的项用深灰色 // 选中的项用红色,未选中的用黑色,禁用的项用深灰色
if (startBtn_) { if (startBtn_) {
startBtn_->setTextColor(selectedIndex_ == 0 ? extra2d::Colors::Red : extra2d::Colors::Black); startBtn_->setTextColor(selectedIndex_ == 0 ? extra2d::Colors::Red
} : extra2d::Colors::Black);
}
if (resumeBtn_) { if (resumeBtn_) {
// "继续关卡"始终显示,但当 g_CurrentLevel == 1 时禁用(深灰色) // "继续关卡"始终显示,但当 g_CurrentLevel == 1 时禁用(深灰色)
if (g_CurrentLevel > 1) { if (g_CurrentLevel > 1) {
// 可用状态:选中为红色,未选中为黑色 // 可用状态:选中为红色,未选中为黑色
resumeBtn_->setTextColor(selectedIndex_ == 1 ? extra2d::Colors::Red : extra2d::Colors::Black); resumeBtn_->setTextColor(selectedIndex_ == 1 ? extra2d::Colors::Red
} else { : extra2d::Colors::Black);
// 禁用状态:深灰色 (RGB: 80, 80, 80) } else {
resumeBtn_->setTextColor(extra2d::Color(80, 80, 80, 255)); // 禁用状态:深灰色 (RGB: 80, 80, 80)
} resumeBtn_->setTextColor(extra2d::Color(80, 80, 80, 255));
} }
}
if (exitBtn_) { if (exitBtn_) {
exitBtn_->setTextColor(selectedIndex_ == 2 ? extra2d::Colors::Red : extra2d::Colors::Black); exitBtn_->setTextColor(selectedIndex_ == 2 ? extra2d::Colors::Red
} : extra2d::Colors::Black);
} }
void StartScene::updateSoundIcon() {
if (!soundIcon_) return;
auto& app = extra2d::Application::instance();
auto& resources = app.resources();
auto soundOn = resources.loadTexture("assets/images/soundon.png");
auto soundOff = resources.loadTexture("assets/images/soundoff.png");
if (soundOn && soundOff) {
soundIcon_->setTexture(g_SoundOpen ? soundOn : soundOff);
}
} }
/**
* @brief
*/
void StartScene::executeMenuItem() { void StartScene::executeMenuItem() {
// 始终有3个选项但"继续关卡"(索引1)在 g_CurrentLevel == 1 时禁用 // 始终有3个选项但"继续关卡"(索引1)在 g_CurrentLevel == 1 时禁用
switch (selectedIndex_) { switch (selectedIndex_) {
case 0: case 0:
startNewGame(); startNewGame();
break; break;
case 1: case 1:
// 只有当 g_CurrentLevel > 1 时才能选择"继续关卡" // 只有当 g_CurrentLevel > 1 时才能选择"继续关卡"
if (g_CurrentLevel > 1) { if (g_CurrentLevel > 1) {
continueGame(); continueGame();
}
break;
case 2:
exitGame();
break;
} }
break;
case 2:
exitGame();
break;
}
} }
/**
* @brief
*/
void StartScene::startNewGame() { void StartScene::startNewGame() {
extra2d::Application::instance().scenes().replaceScene( extra2d::Application::instance().scenes().replaceScene(
extra2d::makePtr<PlayScene>(1), extra2d::TransitionType::Fade, 0.25f); extra2d::makePtr<PlayScene>(1), extra2d::TransitionType::Fade, 0.5f);
} }
/**
* @brief
*/
void StartScene::continueGame() { void StartScene::continueGame() {
extra2d::Application::instance().scenes().replaceScene( extra2d::Application::instance().scenes().replaceScene(
extra2d::makePtr<PlayScene>(g_CurrentLevel), extra2d::TransitionType::Fade, 0.25f); extra2d::makePtr<PlayScene>(g_CurrentLevel),
extra2d::TransitionType::Fade, 0.5f);
} }
void StartScene::exitGame() { /**
extra2d::Application::instance().quit(); * @brief 退
} */
void StartScene::exitGame() { extra2d::Application::instance().quit(); }
} // namespace pushbox } // namespace pushbox

View File

@ -1,28 +1,66 @@
// ============================================================================
// StartScene.h - Push Box 开始场景
// ============================================================================
#pragma once #pragma once
#include "BaseScene.h"
#include <extra2d/extra2d.h> #include <extra2d/extra2d.h>
namespace pushbox { namespace pushbox {
class StartScene : public extra2d::Scene { /**
* @brief Push Box
*/
class StartScene : public BaseScene {
public: public:
/**
* @brief
*/
StartScene(); StartScene();
/**
* @brief
*/
void onEnter() override; void onEnter() override;
/**
* @brief
* @param dt
*/
void onUpdate(float dt) override; void onUpdate(float dt) override;
private: private:
/**
* @brief
*/
void updateMenuColors(); void updateMenuColors();
void updateSoundIcon();
/**
* @brief
*/
void executeMenuItem(); void executeMenuItem();
/**
* @brief
*/
void startNewGame(); void startNewGame();
/**
* @brief
*/
void continueGame(); void continueGame();
/**
* @brief 退
*/
void exitGame(); void exitGame();
extra2d::Ptr<extra2d::FontAtlas> font_; extra2d::Ptr<extra2d::FontAtlas> font_;
extra2d::Ptr<extra2d::Button> startBtn_; extra2d::Ptr<extra2d::Button> startBtn_;
extra2d::Ptr<extra2d::Button> resumeBtn_; extra2d::Ptr<extra2d::Button> resumeBtn_;
extra2d::Ptr<extra2d::Button> exitBtn_; extra2d::Ptr<extra2d::Button> exitBtn_;
extra2d::Ptr<extra2d::Sprite> soundIcon_; extra2d::Ptr<extra2d::Button> soundBtn_;
int selectedIndex_ = 0; int selectedIndex_ = 0;
int menuCount_ = 3; int menuCount_ = 3;
}; };

View File

@ -1,15 +1,20 @@
// ============================================================================
// SuccessScene.cpp - Push Box 通关场景实现
// ============================================================================
#include "SuccessScene.h" #include "SuccessScene.h"
#include <extra2d/extra2d.h> #include <extra2d/extra2d.h>
namespace pushbox { namespace pushbox {
SuccessScene::SuccessScene() { SuccessScene::SuccessScene() : BaseScene() {
auto& app = extra2d::Application::instance(); // BaseScene 已处理视口设置
auto& config = app.getConfig();
setViewportSize(static_cast<float>(config.width), static_cast<float>(config.height));
} }
/**
* @brief
*/
static extra2d::Ptr<extra2d::FontAtlas> loadMenuFont() { static extra2d::Ptr<extra2d::FontAtlas> loadMenuFont() {
auto& resources = extra2d::Application::instance().resources(); auto& resources = extra2d::Application::instance().resources();
auto font = resources.loadFont("assets/font.ttf", 28); auto font = resources.loadFont("assets/font.ttf", 28);
@ -17,16 +22,15 @@ static extra2d::Ptr<extra2d::FontAtlas> loadMenuFont() {
} }
void SuccessScene::onEnter() { void SuccessScene::onEnter() {
Scene::onEnter(); BaseScene::onEnter();
auto& app = extra2d::Application::instance(); auto& app = extra2d::Application::instance();
auto& resources = app.resources(); auto& resources = app.resources();
setBackgroundColor(extra2d::Colors::Black);
if (getChildren().empty()) { if (getChildren().empty()) {
// 获取窗口尺寸 // 使用游戏逻辑分辨率
float screenW = static_cast<float>(app.getConfig().width); float screenW = GAME_WIDTH;
float screenH = static_cast<float>(app.getConfig().height); float screenH = GAME_HEIGHT;
auto bgTex = resources.loadTexture("assets/images/success.jpg"); auto bgTex = resources.loadTexture("assets/images/success.jpg");
if (bgTex) { if (bgTex) {
@ -61,7 +65,7 @@ void SuccessScene::onEnter() {
} }
void SuccessScene::onUpdate(float dt) { void SuccessScene::onUpdate(float dt) {
Scene::onUpdate(dt); BaseScene::onUpdate(dt);
auto& app = extra2d::Application::instance(); auto& app = extra2d::Application::instance();
auto& input = app.input(); auto& input = app.input();
@ -69,8 +73,8 @@ void SuccessScene::onUpdate(float dt) {
// A键确认返回主菜单 // A键确认返回主菜单
if (input.isButtonPressed(extra2d::GamepadButton::A)) { if (input.isButtonPressed(extra2d::GamepadButton::A)) {
auto& scenes = extra2d::Application::instance().scenes(); auto& scenes = extra2d::Application::instance().scenes();
scenes.popScene(extra2d::TransitionType::Fade, 0.2f); scenes.popScene(extra2d::TransitionType::Fade, 0.5f);
scenes.popScene(extra2d::TransitionType::Fade, 0.2f); scenes.popScene(extra2d::TransitionType::Fade, 0.5f);
} }
} }

View File

@ -1,13 +1,33 @@
// ============================================================================
// SuccessScene.h - Push Box 通关场景
// ============================================================================
#pragma once #pragma once
#include "BaseScene.h"
#include <extra2d/extra2d.h> #include <extra2d/extra2d.h>
namespace pushbox { namespace pushbox {
class SuccessScene : public extra2d::Scene { /**
* @brief Push Box
*/
class SuccessScene : public BaseScene {
public: public:
/**
* @brief
*/
SuccessScene(); SuccessScene();
/**
* @brief
*/
void onEnter() override; void onEnter() override;
/**
* @brief
* @param dt
*/
void onUpdate(float dt) override; void onUpdate(float dt) override;
private: private: