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:
parent
4f641a2854
commit
b78c493590
|
|
@ -90,7 +90,7 @@ public:
|
|||
// 便捷方法
|
||||
// ------------------------------------------------------------------------
|
||||
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 totalTime() const { return totalTime_; }
|
||||
|
|
|
|||
|
|
@ -29,7 +29,12 @@
|
|||
#include <extra2d/scene/sprite.h>
|
||||
#include <extra2d/scene/shape_node.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
|
||||
#include <extra2d/animation/sprite_frame.h>
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ public:
|
|||
// 渲染和更新
|
||||
// ------------------------------------------------------------------------
|
||||
void renderScene(RenderBackend &renderer);
|
||||
void renderContent(RenderBackend &renderer);
|
||||
virtual void renderContent(RenderBackend &renderer);
|
||||
void updateScene(float dt);
|
||||
void collectRenderCommands(std::vector<RenderCommand> &commands,
|
||||
int parentZOrder = 0) override;
|
||||
|
|
@ -92,7 +92,12 @@ protected:
|
|||
void onEnter() override;
|
||||
void onExit() override;
|
||||
|
||||
// 过渡场景生命周期回调(供 TransitionScene 使用)
|
||||
virtual void onExitTransitionDidStart() {}
|
||||
virtual void onEnterTransitionDidFinish() {}
|
||||
|
||||
friend class SceneManager;
|
||||
friend class TransitionScene;
|
||||
|
||||
private:
|
||||
Color backgroundColor_ = Colors::Black;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/scene/scene.h>
|
||||
#include <extra2d/scene/transition_scene.h>
|
||||
#include <functional>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
|
|
@ -12,21 +13,7 @@ namespace extra2d {
|
|||
|
||||
// 前向声明
|
||||
struct RenderCommand;
|
||||
class Transition;
|
||||
|
||||
// ============================================================================
|
||||
// 场景切换特效类型
|
||||
// ============================================================================
|
||||
enum class TransitionType {
|
||||
None,
|
||||
Fade,
|
||||
SlideLeft,
|
||||
SlideRight,
|
||||
SlideUp,
|
||||
SlideDown,
|
||||
Scale,
|
||||
Flip
|
||||
};
|
||||
class TransitionScene;
|
||||
|
||||
// ============================================================================
|
||||
// 场景管理器 - 管理场景的生命周期和切换
|
||||
|
|
@ -116,27 +103,27 @@ public:
|
|||
|
||||
// 场景切换(供 Application 使用)
|
||||
void enterScene(Ptr<Scene> scene);
|
||||
void enterScene(Ptr<Scene> scene, Ptr<class Transition> transition);
|
||||
void enterScene(Ptr<Scene> scene, Ptr<TransitionScene> transitionScene);
|
||||
|
||||
private:
|
||||
void doSceneSwitch();
|
||||
void startTransition(Ptr<Scene> from, Ptr<Scene> to, TransitionType type,
|
||||
float duration, Function<void()> stackAction);
|
||||
void updateTransition(float dt);
|
||||
void finishTransition();
|
||||
void dispatchPointerEvents(Scene &scene);
|
||||
|
||||
// 创建过渡场景
|
||||
Ptr<TransitionScene> createTransitionScene(TransitionType type,
|
||||
float duration,
|
||||
Ptr<Scene> inScene);
|
||||
|
||||
std::stack<Ptr<Scene>> sceneStack_;
|
||||
std::unordered_map<std::string, Ptr<Scene>> namedScenes_;
|
||||
|
||||
// Transition state
|
||||
bool isTransitioning_ = false;
|
||||
TransitionType currentTransition_ = TransitionType::None;
|
||||
float transitionDuration_ = 0.0f;
|
||||
float transitionElapsed_ = 0.0f;
|
||||
Ptr<Scene> outgoingScene_;
|
||||
Ptr<Scene> incomingScene_;
|
||||
Ptr<Transition> activeTransition_;
|
||||
Ptr<TransitionScene> activeTransitionScene_;
|
||||
Function<void()> transitionStackAction_;
|
||||
TransitionCallback transitionCallback_;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -95,6 +95,8 @@ public:
|
|||
// 边框设置
|
||||
// ------------------------------------------------------------------------
|
||||
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,
|
||||
Ptr<Texture> pressed = nullptr);
|
||||
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 setCustomSize(const Vec2 &size);
|
||||
void setCustomSize(float width, float height);
|
||||
|
|
@ -131,6 +149,63 @@ public:
|
|||
// ------------------------------------------------------------------------
|
||||
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;
|
||||
|
||||
protected:
|
||||
|
|
@ -170,6 +245,12 @@ private:
|
|||
bool useImageBackground_ = 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);
|
||||
float borderWidth_ = 1.0f;
|
||||
|
|
@ -188,48 +269,10 @@ private:
|
|||
bool hovered_ = false;
|
||||
bool pressed_ = false;
|
||||
|
||||
Function<void()> onClick_;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 切换按钮 - 点击切换两种状态(支持图片和文字)
|
||||
// ============================================================================
|
||||
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_;
|
||||
// 切换模式相关
|
||||
bool toggleMode_ = false;
|
||||
bool isOn_ = false;
|
||||
Function<void(bool)> onStateChange_;
|
||||
|
||||
// 状态文字
|
||||
std::string textOff_, textOn_;
|
||||
|
|
@ -240,8 +283,7 @@ private:
|
|||
Color textColorOn_ = Colors::White;
|
||||
bool useStateTextColor_ = false;
|
||||
|
||||
bool isOn_ = false;
|
||||
Function<void(bool)> onStateChange_;
|
||||
Function<void()> onClick_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -364,11 +364,11 @@ Camera &Application::camera() { return *camera_; }
|
|||
void Application::enterScene(Ptr<Scene> scene) { enterScene(scene, nullptr); }
|
||||
|
||||
void Application::enterScene(Ptr<Scene> scene,
|
||||
Ptr<class Transition> transition) {
|
||||
Ptr<class TransitionScene> transitionScene) {
|
||||
if (sceneManager_ && scene) {
|
||||
scene->setViewportSize(static_cast<float>(window_->getWidth()),
|
||||
static_cast<float>(window_->getHeight()));
|
||||
sceneManager_->enterScene(scene, transition);
|
||||
sceneManager_->enterScene(scene, transitionScene);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,57 +4,16 @@
|
|||
#include <extra2d/graphics/render_command.h>
|
||||
#include <extra2d/platform/input.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>
|
||||
|
||||
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 {
|
||||
|
||||
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,
|
||||
Ptr<class Transition> transition) {
|
||||
Ptr<TransitionScene> transitionScene) {
|
||||
if (!scene || isTransitioning_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!transition) {
|
||||
// 如果没有过渡场景,使用无过渡切换
|
||||
if (!transitionScene) {
|
||||
enterScene(scene);
|
||||
return;
|
||||
}
|
||||
|
||||
auto current = getCurrentScene();
|
||||
if (!current) {
|
||||
enterScene(scene);
|
||||
return;
|
||||
}
|
||||
|
||||
// 在过渡开始前,发送 UIHoverExit 给当前悬停的节点,重置按钮状态
|
||||
if (hoverTarget_) {
|
||||
|
|
@ -173,32 +137,39 @@ void SceneManager::enterScene(Ptr<Scene> scene,
|
|||
captureTarget_ = nullptr;
|
||||
hasLastPointerWorld_ = false;
|
||||
|
||||
transition->start(current, scene);
|
||||
outgoingScene_ = current;
|
||||
incomingScene_ = scene;
|
||||
activeTransition_ = transition;
|
||||
currentTransition_ = TransitionType::None;
|
||||
// 设置过渡场景
|
||||
transitionScene->setOutScene(current);
|
||||
transitionScene->setFinishCallback([this]() { finishTransition(); });
|
||||
|
||||
// 暂停当前场景
|
||||
current->pause();
|
||||
|
||||
// 推入过渡场景(作为中介场景)
|
||||
transitionScene->onEnter();
|
||||
transitionScene->onAttachToScene(transitionScene.get());
|
||||
sceneStack_.push(transitionScene);
|
||||
|
||||
isTransitioning_ = true;
|
||||
transitionStackAction_ = [this]() {
|
||||
activeTransitionScene_ = transitionScene;
|
||||
transitionStackAction_ = [this, transitionScene]() {
|
||||
// 退出旧场景
|
||||
auto outgoing = outgoingScene_;
|
||||
if (!sceneStack_.empty() && outgoing && sceneStack_.top() == outgoing) {
|
||||
outgoing->onExit();
|
||||
outgoing->onDetachFromScene();
|
||||
auto outScene = transitionScene->getOutScene();
|
||||
if (!sceneStack_.empty() && outScene) {
|
||||
// 过渡场景已经在栈顶,弹出它
|
||||
if (sceneStack_.top().get() == transitionScene.get()) {
|
||||
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,
|
||||
|
|
@ -216,19 +187,26 @@ void SceneManager::replaceScene(Ptr<Scene> scene, TransitionType transition,
|
|||
|
||||
startTransition(oldScene, scene, transition, duration, [this]() {
|
||||
// 过渡完成后,退出旧场景并从堆栈中移除
|
||||
auto outgoing = outgoingScene_;
|
||||
auto incoming = incomingScene_;
|
||||
if (!sceneStack_.empty() && outgoing && sceneStack_.top() == outgoing) {
|
||||
outgoing->onExit();
|
||||
outgoing->onDetachFromScene();
|
||||
if (!sceneStack_.empty() && activeTransitionScene_) {
|
||||
// 弹出过渡场景
|
||||
if (sceneStack_.top().get() == activeTransitionScene_.get()) {
|
||||
sceneStack_.pop();
|
||||
}
|
||||
// 将新场景推入堆栈并调用 onEnter
|
||||
if (incoming) {
|
||||
sceneStack_.push(incoming);
|
||||
if (!incoming->isRunning()) {
|
||||
incoming->onEnter();
|
||||
incoming->onAttachToScene(incoming.get());
|
||||
|
||||
// 退出旧场景
|
||||
auto outScene = activeTransitionScene_->getOutScene();
|
||||
if (outScene) {
|
||||
outScene->onExit();
|
||||
outScene->onDetachFromScene();
|
||||
}
|
||||
}
|
||||
|
||||
// 将新场景推入堆栈
|
||||
if (activeTransitionScene_) {
|
||||
auto inScene = activeTransitionScene_->getInScene();
|
||||
if (inScene) {
|
||||
inScene->onAttachToScene(inScene.get());
|
||||
sceneStack_.push(inScene);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -267,12 +245,19 @@ void SceneManager::pushScene(Ptr<Scene> scene, TransitionType transition,
|
|||
auto currentScene = sceneStack_.top();
|
||||
|
||||
startTransition(currentScene, scene, transition, duration, [this]() {
|
||||
// 过渡完成后,将新场景推入堆栈并调用 onEnter
|
||||
if (incomingScene_) {
|
||||
sceneStack_.push(incomingScene_);
|
||||
if (!incomingScene_->isRunning()) {
|
||||
incomingScene_->onEnter();
|
||||
incomingScene_->onAttachToScene(incomingScene_.get());
|
||||
// 过渡完成后,将新场景推入堆栈
|
||||
if (!sceneStack_.empty() && activeTransitionScene_) {
|
||||
// 弹出过渡场景
|
||||
if (sceneStack_.top().get() == activeTransitionScene_.get()) {
|
||||
sceneStack_.pop();
|
||||
}
|
||||
}
|
||||
|
||||
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]() {
|
||||
// 过渡完成后,退出当前场景并从堆栈中移除
|
||||
auto outgoing = outgoingScene_;
|
||||
if (!sceneStack_.empty() && outgoing && sceneStack_.top() == outgoing) {
|
||||
outgoing->onExit();
|
||||
outgoing->onDetachFromScene();
|
||||
if (!sceneStack_.empty() && activeTransitionScene_) {
|
||||
// 弹出过渡场景
|
||||
if (sceneStack_.top().get() == activeTransitionScene_.get()) {
|
||||
sceneStack_.pop();
|
||||
}
|
||||
// 恢复前一个场景
|
||||
auto incoming = incomingScene_;
|
||||
if (!sceneStack_.empty() && incoming && sceneStack_.top() == incoming) {
|
||||
if (!incoming->isRunning()) {
|
||||
incoming->onEnter();
|
||||
incoming->onAttachToScene(incoming.get());
|
||||
|
||||
// 退出当前场景
|
||||
auto outScene = activeTransitionScene_->getOutScene();
|
||||
if (outScene) {
|
||||
outScene->onExit();
|
||||
outScene->onDetachFromScene();
|
||||
}
|
||||
}
|
||||
|
||||
// 恢复前一个场景
|
||||
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]() {
|
||||
// 退出所有场景直到根场景
|
||||
while (!sceneStack_.empty() && sceneStack_.top() != root) {
|
||||
while (!sceneStack_.empty() && sceneStack_.top().get() != root.get()) {
|
||||
auto scene = sceneStack_.top();
|
||||
scene->onExit();
|
||||
scene->onDetachFromScene();
|
||||
sceneStack_.pop();
|
||||
}
|
||||
|
||||
// 恢复根场景
|
||||
if (!sceneStack_.empty() && sceneStack_.top() == root) {
|
||||
if (!root->isRunning()) {
|
||||
root->onEnter();
|
||||
root->onAttachToScene(root.get());
|
||||
}
|
||||
if (!sceneStack_.empty() && sceneStack_.top().get() == root.get()) {
|
||||
root->resume();
|
||||
}
|
||||
});
|
||||
|
|
@ -409,12 +397,9 @@ void SceneManager::popToScene(const std::string &name,
|
|||
scene->onDetachFromScene();
|
||||
sceneStack_.pop();
|
||||
}
|
||||
|
||||
// 恢复目标场景
|
||||
if (!sceneStack_.empty() && sceneStack_.top() == target) {
|
||||
if (!target->isRunning()) {
|
||||
target->onEnter();
|
||||
target->onAttachToScene(target.get());
|
||||
}
|
||||
target->resume();
|
||||
}
|
||||
});
|
||||
|
|
@ -479,7 +464,7 @@ bool SceneManager::hasScene(const std::string &name) const {
|
|||
|
||||
void SceneManager::update(float dt) {
|
||||
if (isTransitioning_) {
|
||||
updateTransition(dt);
|
||||
// 过渡场景在栈顶,正常更新即可
|
||||
hoverTarget_ = nullptr;
|
||||
captureTarget_ = nullptr;
|
||||
hasLastPointerWorld_ = false;
|
||||
|
|
@ -496,13 +481,7 @@ void SceneManager::update(float dt) {
|
|||
|
||||
void SceneManager::render(RenderBackend &renderer) {
|
||||
Color clearColor = Colors::Black;
|
||||
if (isTransitioning_) {
|
||||
if (outgoingScene_) {
|
||||
clearColor = outgoingScene_->getBackgroundColor();
|
||||
} else if (incomingScene_) {
|
||||
clearColor = incomingScene_->getBackgroundColor();
|
||||
}
|
||||
} else if (!sceneStack_.empty()) {
|
||||
if (!sceneStack_.empty()) {
|
||||
clearColor = sceneStack_.top()->getBackgroundColor();
|
||||
}
|
||||
|
||||
|
|
@ -510,10 +489,7 @@ void SceneManager::render(RenderBackend &renderer) {
|
|||
clearColor.r, clearColor.g, clearColor.b);
|
||||
renderer.beginFrame(clearColor);
|
||||
|
||||
if (isTransitioning_ && activeTransition_) {
|
||||
E2D_LOG_TRACE("SceneManager::render - rendering transition");
|
||||
activeTransition_->render(renderer);
|
||||
} else if (!sceneStack_.empty()) {
|
||||
if (!sceneStack_.empty()) {
|
||||
E2D_LOG_TRACE("SceneManager::render - rendering scene content");
|
||||
sceneStack_.top()->renderContent(renderer);
|
||||
} else {
|
||||
|
|
@ -525,13 +501,7 @@ void SceneManager::render(RenderBackend &renderer) {
|
|||
}
|
||||
|
||||
void SceneManager::collectRenderCommands(std::vector<RenderCommand> &commands) {
|
||||
if (isTransitioning_ && outgoingScene_) {
|
||||
// During transition, collect commands from both scenes
|
||||
outgoingScene_->collectRenderCommands(commands, 0);
|
||||
if (incomingScene_) {
|
||||
incomingScene_->collectRenderCommands(commands, 0);
|
||||
}
|
||||
} else if (!sceneStack_.empty()) {
|
||||
if (!sceneStack_.empty()) {
|
||||
sceneStack_.top()->collectRenderCommands(commands, 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -555,18 +525,14 @@ void SceneManager::startTransition(Ptr<Scene> from, Ptr<Scene> to,
|
|||
return;
|
||||
}
|
||||
|
||||
// 使用工厂映射替代 switch
|
||||
Ptr<Transition> transition;
|
||||
size_t typeIndex = static_cast<size_t>(type);
|
||||
if (typeIndex < TRANSITION_FACTORY_COUNT) {
|
||||
transition = TRANSITION_FACTORIES[typeIndex](duration);
|
||||
} else {
|
||||
// 默认使用 Fade 过渡
|
||||
transition = TRANSITION_FACTORIES[0](duration);
|
||||
// 创建过渡场景
|
||||
auto transitionScene = createTransitionScene(type, duration, to);
|
||||
if (!transitionScene) {
|
||||
// 回退到无过渡切换
|
||||
replaceScene(to);
|
||||
return;
|
||||
}
|
||||
|
||||
transition->start(from, to);
|
||||
|
||||
// 在过渡开始前,发送 UIHoverExit 给当前悬停的节点,重置按钮状态
|
||||
if (hoverTarget_) {
|
||||
Event evt;
|
||||
|
|
@ -578,29 +544,55 @@ void SceneManager::startTransition(Ptr<Scene> from, Ptr<Scene> to,
|
|||
captureTarget_ = nullptr;
|
||||
hasLastPointerWorld_ = false;
|
||||
|
||||
// 设置过渡场景
|
||||
transitionScene->setOutScene(from);
|
||||
transitionScene->setFinishCallback([this]() { finishTransition(); });
|
||||
|
||||
// 暂停当前场景
|
||||
from->pause();
|
||||
|
||||
// 推入过渡场景(作为中介场景)
|
||||
transitionScene->onEnter();
|
||||
transitionScene->onAttachToScene(transitionScene.get());
|
||||
sceneStack_.push(transitionScene);
|
||||
|
||||
isTransitioning_ = true;
|
||||
currentTransition_ = type;
|
||||
transitionDuration_ = duration;
|
||||
transitionElapsed_ = 0.0f;
|
||||
outgoingScene_ = from;
|
||||
incomingScene_ = to;
|
||||
activeTransition_ = transition;
|
||||
activeTransitionScene_ = transitionScene;
|
||||
transitionStackAction_ = std::move(stackAction);
|
||||
|
||||
// 注意:不在此处调用新场景的 onEnter,由 transitionStackAction_
|
||||
// 在过渡完成后调用
|
||||
}
|
||||
|
||||
void SceneManager::updateTransition(float dt) {
|
||||
transitionElapsed_ += dt;
|
||||
|
||||
if (activeTransition_) {
|
||||
activeTransition_->update(dt);
|
||||
if (activeTransition_->isFinished()) {
|
||||
finishTransition();
|
||||
Ptr<TransitionScene> SceneManager::createTransitionScene(TransitionType type,
|
||||
float duration,
|
||||
Ptr<Scene> inScene) {
|
||||
if (!inScene) {
|
||||
return nullptr;
|
||||
}
|
||||
} else {
|
||||
finishTransition();
|
||||
|
||||
switch (type) {
|
||||
case TransitionType::Fade:
|
||||
return TransitionFadeScene::create(duration, inScene);
|
||||
case TransitionType::SlideLeft:
|
||||
return TransitionSlideScene::create(duration, inScene,
|
||||
TransitionDirection::Left);
|
||||
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);
|
||||
}
|
||||
|
||||
outgoingScene_.reset();
|
||||
incomingScene_.reset();
|
||||
activeTransition_.reset();
|
||||
activeTransitionScene_.reset();
|
||||
transitionStackAction_ = nullptr;
|
||||
|
||||
if (transitionCallback_) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <extra2d/app/application.h>
|
||||
#include <extra2d/core/string.h>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <extra2d/ui/button.h>
|
||||
#include <extra2d/core/string.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
|
|
@ -37,21 +37,21 @@ Button::Button() {
|
|||
dispatcher.addListener(EventType::UIReleased,
|
||||
[this](Event &) { pressed_ = false; });
|
||||
dispatcher.addListener(EventType::UIClicked, [this](Event &) {
|
||||
if (onClick_)
|
||||
if (toggleMode_) {
|
||||
toggle();
|
||||
}
|
||||
if (onClick_) {
|
||||
onClick_();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Button::Button(const std::string &text) : Button() {
|
||||
text_ = text;
|
||||
}
|
||||
Button::Button(const std::string &text) : Button() { text_ = text; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 静态创建方法
|
||||
// ------------------------------------------------------------------------
|
||||
Ptr<Button> Button::create() {
|
||||
return makePtr<Button>();
|
||||
}
|
||||
Ptr<Button> Button::create() { return makePtr<Button>(); }
|
||||
|
||||
Ptr<Button> Button::create(const std::string &text) {
|
||||
return makePtr<Button>(text);
|
||||
|
|
@ -197,9 +197,7 @@ void Button::setPadding(const Vec2 &padding) {
|
|||
}
|
||||
}
|
||||
|
||||
void Button::setTextColor(const Color &color) {
|
||||
textColor_ = color;
|
||||
}
|
||||
void Button::setTextColor(const Color &color) { textColor_ = color; }
|
||||
|
||||
void Button::setBackgroundColor(const Color &normal, const Color &hover,
|
||||
const Color &pressed) {
|
||||
|
|
@ -229,9 +227,37 @@ void Button::setOnClick(Function<void()> callback) {
|
|||
onClick_ = std::move(callback);
|
||||
}
|
||||
|
||||
void Button::setHoverCursor(CursorShape cursor) {
|
||||
hoverCursor_ = cursor;
|
||||
void Button::setToggleMode(bool enabled) { toggleMode_ = enabled; }
|
||||
|
||||
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,
|
||||
Ptr<Texture> pressed) {
|
||||
|
|
@ -256,12 +282,33 @@ void Button::setBackgroundImage(Ptr<Texture> texture, const Rect &rect) {
|
|||
imgPressedRect_ = rect;
|
||||
useImageBackground_ = (texture != nullptr);
|
||||
useTextureRect_ = true;
|
||||
useStateImages_ = false;
|
||||
|
||||
if (useImageBackground_ && scaleMode_ == ImageScaleMode::Original) {
|
||||
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) {
|
||||
scaleMode_ = mode;
|
||||
if (useImageBackground_ && scaleMode_ == ImageScaleMode::Original &&
|
||||
|
|
@ -271,9 +318,7 @@ void Button::setBackgroundImageScaleMode(ImageScaleMode mode) {
|
|||
}
|
||||
}
|
||||
|
||||
void Button::setCustomSize(const Vec2 &size) {
|
||||
setSize(size.x, size.y);
|
||||
}
|
||||
void Button::setCustomSize(const Vec2 &size) { setSize(size.x, size.y); }
|
||||
|
||||
void Button::setCustomSize(float width, float height) {
|
||||
setSize(width, height);
|
||||
|
|
@ -326,15 +371,50 @@ void Button::drawBackgroundImage(RenderBackend &renderer, const Rect &rect) {
|
|||
Texture *texture = nullptr;
|
||||
Rect srcRect;
|
||||
|
||||
// 如果使用状态图片,根据 isOn_ 状态选择对应的图片
|
||||
if (useStateImages_) {
|
||||
if (isOn_) {
|
||||
if (pressed_ && imgOnPressed_) {
|
||||
texture = imgOnPressed_.get();
|
||||
} else if (hovered_ && imgOnHover_) {
|
||||
texture = imgOnHover_.get();
|
||||
} else {
|
||||
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()));
|
||||
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()));
|
||||
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()));
|
||||
srcRect = useTextureRect_
|
||||
? imgNormalRect_
|
||||
: Rect(0, 0, static_cast<float>(imgNormal_->getWidth()),
|
||||
static_cast<float>(imgNormal_->getHeight()));
|
||||
}
|
||||
}
|
||||
|
||||
if (!texture)
|
||||
|
|
@ -349,8 +429,8 @@ void Button::drawBackgroundImage(RenderBackend &renderer, const Rect &rect) {
|
|||
|
||||
Rect destRect(drawPos.x, drawPos.y, drawSize.x, drawSize.y);
|
||||
|
||||
renderer.drawSprite(*texture, destRect, srcRect,
|
||||
Colors::White, 0.0f, Vec2::Zero());
|
||||
renderer.drawSprite(*texture, destRect, srcRect, Colors::White, 0.0f,
|
||||
Vec2::Zero());
|
||||
}
|
||||
|
||||
void Button::drawRoundedRect(RenderBackend &renderer, const Rect &rect,
|
||||
|
|
@ -371,9 +451,11 @@ void Button::drawRoundedRect(RenderBackend &renderer, const Rect &rect,
|
|||
float r = radius;
|
||||
|
||||
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 + 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++) {
|
||||
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++) {
|
||||
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 p2(x + w - r + r * cosf(angle2), y + r + r * sinf(angle2));
|
||||
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++) {
|
||||
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 p2(x + r + r * cosf(angle2), y + h - r + r * sinf(angle2));
|
||||
renderer.drawLine(p1, p2, color, borderWidth_);
|
||||
|
|
@ -513,8 +597,25 @@ void Button::onDrawWidget(RenderBackend &renderer) {
|
|||
renderer.beginSpriteBatch();
|
||||
}
|
||||
|
||||
if (font_ && !text_.empty()) {
|
||||
Vec2 textSize = font_->measureText(text_);
|
||||
if (font_) {
|
||||
// 确定要显示的文字
|
||||
std::string textToDraw;
|
||||
if (useStateText_) {
|
||||
textToDraw = isOn_ ? textOn_ : textOff_;
|
||||
} else {
|
||||
textToDraw = text_;
|
||||
}
|
||||
|
||||
// 确定文字颜色
|
||||
Color colorToUse;
|
||||
if (useStateTextColor_) {
|
||||
colorToUse = isOn_ ? textColorOn_ : textColorOff_;
|
||||
} else {
|
||||
colorToUse = textColor_;
|
||||
}
|
||||
|
||||
if (!textToDraw.empty()) {
|
||||
Vec2 textSize = font_->measureText(textToDraw);
|
||||
|
||||
Vec2 textPos(rect.center().x - textSize.x * 0.5f,
|
||||
rect.center().y - textSize.y * 0.5f);
|
||||
|
|
@ -527,155 +628,9 @@ void Button::onDrawWidget(RenderBackend &renderer) {
|
|||
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;
|
||||
if (useStateText_) {
|
||||
textToDraw = isOn_ ? textOn_ : textOff_;
|
||||
} else {
|
||||
textToDraw = getText();
|
||||
}
|
||||
|
||||
Color colorToUse;
|
||||
if (useStateTextColor_) {
|
||||
colorToUse = isOn_ ? textColorOn_ : textColorOff_;
|
||||
} else {
|
||||
colorToUse = getTextColor();
|
||||
}
|
||||
|
||||
if (!textToDraw.empty()) {
|
||||
Vec2 textSize = font->measureText(textToDraw);
|
||||
Vec2 textPos(rect.center().x - textSize.x * 0.5f,
|
||||
rect.center().y - textSize.y * 0.5f);
|
||||
|
||||
colorToUse.a = 1.0f;
|
||||
|
||||
renderer.drawText(*font, textToDraw, textPos, colorToUse);
|
||||
renderer.drawText(*font_, textToDraw, textPos, colorToUse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Extra2D 的场景系统提供了游戏内容的分层管理和切换功能。本
|
|||
|
||||
## 完整示例
|
||||
|
||||
参考 `examples/flappy_bird/` 中的实现:
|
||||
参考 `examples/flappy_bird/` 和 `examples/push_box/` 中的实现:
|
||||
|
||||
- `BaseScene.h/cpp` - 基础场景类(视口适配)
|
||||
- `StartScene.h/cpp` - 开始菜单场景
|
||||
|
|
@ -193,6 +193,14 @@ void GameScene::onUpdate(float dt) {
|
|||
4. **所有坐标使用逻辑分辨率** - 不依赖窗口实际大小
|
||||
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
|
||||
// 进入场景(无过渡)
|
||||
app.enterScene(makePtr<GameScene>());
|
||||
auto& scenes = app.scenes();
|
||||
|
||||
// 进入场景(有过渡效果)
|
||||
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, // 向左滑动
|
||||
SlideRight, // 向右滑动
|
||||
SlideUp, // 向上滑动
|
||||
SlideDown // 向下滑动
|
||||
SlideDown, // 向下滑动
|
||||
Scale, // 缩放过渡
|
||||
Flip, // 翻转过渡
|
||||
Box // 盒子过渡
|
||||
};
|
||||
```
|
||||
|
||||
## 场景管理器
|
||||
### 场景管理器
|
||||
|
||||
通过 `app.scenes()` 访问场景管理器:
|
||||
|
||||
|
|
@ -270,13 +295,25 @@ enum class TransitionType {
|
|||
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>)
|
||||
↓
|
||||
进入场景 (enterScene)
|
||||
进入场景 (runWithScene/replaceScene/pushScene)
|
||||
↓
|
||||
onEnter() - 初始化资源
|
||||
↓
|
||||
|
|
@ -411,10 +448,12 @@ private:
|
|||
}
|
||||
|
||||
void executeMenuItem() {
|
||||
auto& scenes = Application::instance().scenes();
|
||||
|
||||
switch (selectedIndex_) {
|
||||
case 0:
|
||||
Application::instance().scenes().replaceScene(
|
||||
makePtr<GameScene>(), TransitionType::Fade, 0.25f);
|
||||
scenes.replaceScene(makePtr<GameScene>(),
|
||||
TransitionType::Fade, 0.25f);
|
||||
break;
|
||||
case 1:
|
||||
// 打开设置
|
||||
|
|
@ -438,6 +477,8 @@ private:
|
|||
2. **在 onExit 中清理资源** - 避免内存泄漏
|
||||
3. **使用过渡效果** - 提升用户体验
|
||||
4. **分离场景逻辑** - 每个场景负责自己的功能
|
||||
5. **使用视口适配** - 确保游戏在不同分辨率下正确显示
|
||||
6. **正确处理窗口大小变化** - 在 `onRender()` 中检测并更新视口
|
||||
|
||||
## 下一步
|
||||
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ button->setBorder(Colors::White, 2.0f);
|
|||
button->setRoundedCornersEnabled(true);
|
||||
button->setCornerRadius(8.0f);
|
||||
|
||||
// 图片背景
|
||||
// 图片背景(普通按钮)
|
||||
button->setBackgroundImage(normalTex, hoverTex, pressedTex);
|
||||
button->setBackgroundImageScaleMode(ImageScaleMode::ScaleFit);
|
||||
|
||||
|
|
@ -117,6 +117,51 @@ button->setBackgroundImageScaleMode(ImageScaleMode::ScaleFit);
|
|||
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
|
||||
|
|
@ -245,6 +290,7 @@ void GameOverLayer::onUpdate(float dt) {
|
|||
- **动画播放期间**:禁用按钮,防止用户过早操作
|
||||
- **加载过程中**:禁用按钮,显示加载状态
|
||||
- **条件限制**:当条件不满足时禁用按钮(如未选择关卡)
|
||||
- **切换按钮**:音效开关、设置开关等需要显示两种状态的按钮
|
||||
|
||||
## 文本(Text)
|
||||
|
||||
|
|
@ -350,11 +396,11 @@ checkBox->setPosition(Vec2(100, 200));
|
|||
checkBox->setChecked(true);
|
||||
|
||||
// 方式2:带标签
|
||||
auto checkBox = CheckBox::create("启用音效");
|
||||
checkBox = CheckBox::create("启用音效");
|
||||
checkBox->setPosition(Vec2(100, 200));
|
||||
|
||||
// 方式3:链式调用
|
||||
auto checkBox = CheckBox::create("启用音效")
|
||||
checkBox = CheckBox::create("启用音效")
|
||||
->withPosition(100, 200)
|
||||
->withFont(font)
|
||||
->withTextColor(Colors::White);
|
||||
|
|
@ -620,18 +666,24 @@ public:
|
|||
title->setAlignment(Alignment::Center);
|
||||
addChild(title);
|
||||
|
||||
// 音效开关
|
||||
// 音效开关(使用切换按钮)
|
||||
auto soundLabel = Label::create("音效", font_);
|
||||
soundLabel->setPosition(Vec2(200, 200));
|
||||
addChild(soundLabel);
|
||||
|
||||
soundCheck_ = CheckBox::create();
|
||||
soundCheck_->setPosition(Vec2(350, 200));
|
||||
soundCheck_->setChecked(true);
|
||||
soundCheck_->setOnStateChange([this](bool checked) {
|
||||
E2D_LOG_INFO("音效: {}", checked ? "开启" : "关闭");
|
||||
auto soundOn = resources.loadTexture("assets/sound_on.png");
|
||||
auto soundOff = resources.loadTexture("assets/sound_off.png");
|
||||
|
||||
soundToggle_ = Button::create();
|
||||
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_);
|
||||
|
|
@ -685,7 +737,7 @@ public:
|
|||
|
||||
private:
|
||||
Ptr<FontAtlas> font_;
|
||||
Ptr<CheckBox> soundCheck_;
|
||||
Ptr<Button> soundToggle_;
|
||||
Ptr<Slider> volumeSlider_;
|
||||
};
|
||||
```
|
||||
|
|
@ -697,6 +749,7 @@ private:
|
|||
3. **设置合适的锚点** - 使用锚点(0.5, 0.5)让控件中心对齐,方便布局
|
||||
4. **复用字体资源** - 避免重复加载相同字体
|
||||
5. **使用回调函数** - 使用 `setOnClick`、`setOnValueChange` 等回调响应用户操作
|
||||
6. **使用切换按钮** - 对于需要显示两种状态的按钮(如开关),使用 `setToggleMode(true)`
|
||||
|
||||
## 下一步
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
// ============================================================================
|
||||
|
||||
#include "BaseScene.h"
|
||||
#include <extra2d/scene/transition_scene.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
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(
|
||||
static_cast<int>(viewportOffsetX_), static_cast<int>(viewportOffsetY_),
|
||||
static_cast<int>(scaledGameWidth_), static_cast<int>(scaledGameHeight_));
|
||||
|
|
@ -78,4 +75,53 @@ void BaseScene::onRender(extra2d::RenderBackend &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
|
||||
|
|
|
|||
|
|
@ -35,6 +35,12 @@ public:
|
|||
*/
|
||||
void onRender(extra2d::RenderBackend &renderer) override;
|
||||
|
||||
/**
|
||||
* @brief 渲染场景内容,确保视口正确设置
|
||||
* @param renderer 渲染后端
|
||||
*/
|
||||
void renderContent(extra2d::RenderBackend &renderer) override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief 更新视口计算,使游戏内容在窗口中居中显示
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ void SplashScene::onUpdate(float dt) {
|
|||
void SplashScene::gotoStartScene() {
|
||||
auto &app = extra2d::Application::instance();
|
||||
app.scenes().replaceScene(extra2d::makePtr<StartScene>(),
|
||||
extra2d::TransitionType::Fade, 0.5f);
|
||||
extra2d::TransitionType::Fade, 2.0f);
|
||||
}
|
||||
|
||||
} // namespace flappybird
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -1,26 +1,29 @@
|
|||
// ============================================================================
|
||||
// PlayScene.cpp - Push Box 游戏场景实现
|
||||
// ============================================================================
|
||||
|
||||
#include "PlayScene.h"
|
||||
|
||||
#include "audio_manager.h"
|
||||
#include "storage.h"
|
||||
#include "StartScene.h"
|
||||
#include "SuccessScene.h"
|
||||
#include "audio_manager.h"
|
||||
#include "storage.h"
|
||||
#include <extra2d/extra2d.h>
|
||||
|
||||
namespace pushbox {
|
||||
|
||||
/**
|
||||
* @brief 加载字体
|
||||
* @param size 字体大小
|
||||
*/
|
||||
static extra2d::Ptr<extra2d::FontAtlas> loadFont(int size) {
|
||||
auto &resources = extra2d::Application::instance().resources();
|
||||
auto font = resources.loadFont("assets/font.ttf", size);
|
||||
return font;
|
||||
}
|
||||
|
||||
PlayScene::PlayScene(int level) {
|
||||
setBackgroundColor(extra2d::Colors::Black);
|
||||
|
||||
PlayScene::PlayScene(int level) : BaseScene() {
|
||||
auto &app = extra2d::Application::instance();
|
||||
auto& config = app.getConfig();
|
||||
setViewportSize(static_cast<float>(config.width), static_cast<float>(config.height));
|
||||
|
||||
auto &resources = app.resources();
|
||||
|
||||
E2D_LOG_INFO("PlayScene: Loading textures...");
|
||||
|
|
@ -37,30 +40,39 @@ PlayScene::PlayScene(int level) {
|
|||
texMan_[4] = resources.loadTexture("assets/images/player/manright.gif");
|
||||
|
||||
texManPush_[1] = resources.loadTexture("assets/images/player/manhandup.gif");
|
||||
texManPush_[2] = resources.loadTexture("assets/images/player/manhanddown.gif");
|
||||
texManPush_[3] = resources.loadTexture("assets/images/player/manhandleft.gif");
|
||||
texManPush_[4] = resources.loadTexture("assets/images/player/manhandright.gif");
|
||||
texManPush_[2] =
|
||||
resources.loadTexture("assets/images/player/manhanddown.gif");
|
||||
texManPush_[3] =
|
||||
resources.loadTexture("assets/images/player/manhandleft.gif");
|
||||
texManPush_[4] =
|
||||
resources.loadTexture("assets/images/player/manhandright.gif");
|
||||
|
||||
font28_ = loadFont(28);
|
||||
font20_ = loadFont(20);
|
||||
|
||||
// 获取窗口尺寸
|
||||
float screenW = static_cast<float>(app.getConfig().width);
|
||||
float screenH = static_cast<float>(app.getConfig().height);
|
||||
// 使用游戏逻辑分辨率
|
||||
float screenW = GAME_WIDTH;
|
||||
float screenH = GAME_HEIGHT;
|
||||
|
||||
// 计算游戏区域居中偏移(假设游戏区域是 640x480)
|
||||
float gameWidth = 640.0f;
|
||||
float gameHeight = 480.0f;
|
||||
float offsetX = (screenW - gameWidth) / 2.0f;
|
||||
float offsetY = (screenH - gameHeight) / 2.0f;
|
||||
// 计算游戏区域居中偏移
|
||||
float offsetX = (screenW - GAME_WIDTH) / 2.0f;
|
||||
float offsetY = (screenH - GAME_HEIGHT) / 2.0f;
|
||||
|
||||
// 音效图标(左上角,与主界面一致)
|
||||
// 音效开关按钮(使用 Button 的切换模式)
|
||||
auto soundOn = resources.loadTexture("assets/images/soundon.png");
|
||||
auto soundOff = resources.loadTexture("assets/images/soundoff.png");
|
||||
if (soundOn && soundOff) {
|
||||
soundIcon_ = extra2d::Sprite::create(g_SoundOpen ? soundOn : soundOff);
|
||||
soundIcon_->setPosition(offsetX + 50.0f, offsetY + 50.0f);
|
||||
addChild(soundIcon_);
|
||||
soundBtn_ = extra2d::Button::create();
|
||||
soundBtn_->setToggleMode(true);
|
||||
soundBtn_->setStateBackgroundImage(soundOff, soundOn);
|
||||
soundBtn_->setOn(g_SoundOpen);
|
||||
soundBtn_->setAnchor(0.0f, 0.0f);
|
||||
soundBtn_->setPosition(offsetX + 50.0f, offsetY + 50.0f);
|
||||
soundBtn_->setOnStateChange([](bool isOn) {
|
||||
g_SoundOpen = isOn;
|
||||
AudioManager::instance().setEnabled(isOn);
|
||||
});
|
||||
addChild(soundBtn_);
|
||||
}
|
||||
|
||||
levelText_ = extra2d::Text::create("", font28_);
|
||||
|
|
@ -96,31 +108,38 @@ PlayScene::PlayScene(int level) {
|
|||
}
|
||||
|
||||
void PlayScene::onEnter() {
|
||||
Scene::onEnter();
|
||||
updateSoundIcon();
|
||||
BaseScene::onEnter();
|
||||
if (soundBtn_) {
|
||||
soundBtn_->setOn(g_SoundOpen);
|
||||
}
|
||||
updateMenuColors();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新菜单颜色
|
||||
*/
|
||||
void PlayScene::updateMenuColors() {
|
||||
// 选中的项用红色,未选中的用白色
|
||||
if (restartText_) {
|
||||
restartText_->setTextColor(menuIndex_ == 0 ? extra2d::Colors::Red : extra2d::Colors::White);
|
||||
restartText_->setTextColor(menuIndex_ == 0 ? extra2d::Colors::Red
|
||||
: extra2d::Colors::White);
|
||||
}
|
||||
if (soundToggleText_) {
|
||||
soundToggleText_->setTextColor(menuIndex_ == 1 ? extra2d::Colors::Red : extra2d::Colors::White);
|
||||
soundToggleText_->setTextColor(menuIndex_ == 1 ? extra2d::Colors::Red
|
||||
: extra2d::Colors::White);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayScene::onUpdate(float dt) {
|
||||
Scene::onUpdate(dt);
|
||||
BaseScene::onUpdate(dt);
|
||||
|
||||
auto &app = extra2d::Application::instance();
|
||||
auto &input = app.input();
|
||||
|
||||
// B 键返回主菜单
|
||||
if (input.isButtonPressed(extra2d::GamepadButton::B)) {
|
||||
app.scenes().replaceScene(
|
||||
extra2d::makePtr<StartScene>(), extra2d::TransitionType::Fade, 0.2f);
|
||||
app.scenes().replaceScene(extra2d::makePtr<StartScene>(),
|
||||
extra2d::TransitionType::Fade, 0.5f);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -130,11 +149,13 @@ void PlayScene::onUpdate(float dt) {
|
|||
return;
|
||||
}
|
||||
|
||||
// X键直接切换音效
|
||||
// X键直接切换音效(备用,按钮也可点击切换)
|
||||
if (input.isButtonPressed(extra2d::GamepadButton::X)) {
|
||||
g_SoundOpen = !g_SoundOpen;
|
||||
AudioManager::instance().setEnabled(g_SoundOpen);
|
||||
updateSoundIcon();
|
||||
if (soundBtn_) {
|
||||
soundBtn_->setOn(g_SoundOpen);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -174,6 +195,9 @@ void PlayScene::onUpdate(float dt) {
|
|||
gameOver();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 执行选中的菜单项
|
||||
*/
|
||||
void PlayScene::executeMenuItem() {
|
||||
switch (menuIndex_) {
|
||||
case 0: // 重开
|
||||
|
|
@ -182,42 +206,31 @@ void PlayScene::executeMenuItem() {
|
|||
case 1: // 切换音效
|
||||
g_SoundOpen = !g_SoundOpen;
|
||||
AudioManager::instance().setEnabled(g_SoundOpen);
|
||||
updateSoundIcon();
|
||||
if (soundBtn_) {
|
||||
soundBtn_->setOn(g_SoundOpen);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 刷新地图显示
|
||||
*/
|
||||
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;
|
||||
// 使用游戏逻辑分辨率
|
||||
float gameWidth = GAME_WIDTH;
|
||||
float gameHeight = GAME_HEIGHT;
|
||||
float baseOffsetX = 0.0f;
|
||||
float baseOffsetY = 0.0f;
|
||||
|
||||
// 在 12x12 网格中居中地图
|
||||
float mapOffsetX = static_cast<float>((12 - map_.width) / 2) * tileW;
|
||||
float mapOffsetY = static_cast<float>((12 - map_.height) / 2) * tileH;
|
||||
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;
|
||||
|
|
@ -259,6 +272,10 @@ void PlayScene::flush() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置关卡
|
||||
* @param level 关卡编号
|
||||
*/
|
||||
void PlayScene::setLevel(int level) {
|
||||
g_CurrentLevel = level;
|
||||
saveCurrentLevel(g_CurrentLevel);
|
||||
|
|
@ -295,6 +312,10 @@ void PlayScene::setLevel(int level) {
|
|||
flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置步数
|
||||
* @param step 步数
|
||||
*/
|
||||
void PlayScene::setStep(int step) {
|
||||
step_ = step;
|
||||
if (stepText_) {
|
||||
|
|
@ -302,12 +323,19 @@ void PlayScene::setStep(int 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) {
|
||||
if (targetX < 0 || targetX >= map_.width || targetY < 0 ||
|
||||
targetY >= map_.height) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -350,7 +378,8 @@ void PlayScene::move(int dx, int dy, int direct) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (map_.value[boxY][boxX].type == TYPE::Wall || map_.value[boxY][boxX].type == TYPE::Box) {
|
||||
if (map_.value[boxY][boxX].type == TYPE::Wall ||
|
||||
map_.value[boxY][boxX].type == TYPE::Box) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -368,6 +397,9 @@ void PlayScene::move(int dx, int dy, int direct) {
|
|||
setStep(step_ + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 游戏通关
|
||||
*/
|
||||
void PlayScene::gameOver() {
|
||||
int bestStep = loadBestStep(g_CurrentLevel, 0);
|
||||
if (bestStep == 0 || step_ < bestStep) {
|
||||
|
|
@ -375,8 +407,8 @@ void PlayScene::gameOver() {
|
|||
}
|
||||
|
||||
if (g_CurrentLevel == MAX_LEVEL) {
|
||||
extra2d::Application::instance().scenes().pushScene(extra2d::makePtr<SuccessScene>(),
|
||||
extra2d::TransitionType::Fade, 0.25f);
|
||||
extra2d::Application::instance().scenes().pushScene(
|
||||
extra2d::makePtr<SuccessScene>(), extra2d::TransitionType::Fade, 0.5f);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +1,76 @@
|
|||
// ============================================================================
|
||||
// PlayScene.h - Push Box 游戏场景
|
||||
// ============================================================================
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BaseScene.h"
|
||||
#include "data.h"
|
||||
#include <extra2d/extra2d.h>
|
||||
|
||||
namespace pushbox {
|
||||
|
||||
class PlayScene : public extra2d::Scene {
|
||||
/**
|
||||
* @brief Push Box 游戏场景
|
||||
*/
|
||||
class PlayScene : public BaseScene {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param level 关卡编号
|
||||
*/
|
||||
explicit PlayScene(int level);
|
||||
|
||||
/**
|
||||
* @brief 场景进入时调用
|
||||
*/
|
||||
void onEnter() override;
|
||||
|
||||
/**
|
||||
* @brief 每帧更新
|
||||
* @param dt 帧间隔时间
|
||||
*/
|
||||
void onUpdate(float dt) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 更新菜单颜色
|
||||
*/
|
||||
void updateMenuColors();
|
||||
|
||||
/**
|
||||
* @brief 执行选中的菜单项
|
||||
*/
|
||||
void executeMenuItem();
|
||||
void updateSoundIcon();
|
||||
|
||||
/**
|
||||
* @brief 刷新地图显示
|
||||
*/
|
||||
void flush();
|
||||
|
||||
/**
|
||||
* @brief 设置关卡
|
||||
* @param level 关卡编号
|
||||
*/
|
||||
void setLevel(int level);
|
||||
|
||||
/**
|
||||
* @brief 设置步数
|
||||
* @param step 步数
|
||||
*/
|
||||
void setStep(int step);
|
||||
|
||||
/**
|
||||
* @brief 移动玩家
|
||||
* @param dx X方向偏移
|
||||
* @param dy Y方向偏移
|
||||
* @param direct 方向(1=上,2=下,3=左,4=右)
|
||||
*/
|
||||
void move(int dx, int dy, int direct);
|
||||
|
||||
/**
|
||||
* @brief 游戏通关
|
||||
*/
|
||||
void gameOver();
|
||||
|
||||
int step_ = 0;
|
||||
|
|
@ -36,7 +87,7 @@ private:
|
|||
extra2d::Ptr<extra2d::Text> soundToggleText_;
|
||||
extra2d::Ptr<extra2d::Node> mapLayer_;
|
||||
|
||||
extra2d::Ptr<extra2d::Sprite> soundIcon_;
|
||||
extra2d::Ptr<extra2d::Button> soundBtn_;
|
||||
|
||||
extra2d::Ptr<extra2d::Texture> texWall_;
|
||||
extra2d::Ptr<extra2d::Texture> texPoint_;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,23 @@
|
|||
// ============================================================================
|
||||
// StartScene.cpp - Push Box 开始场景实现
|
||||
// ============================================================================
|
||||
|
||||
#include "StartScene.h"
|
||||
|
||||
#include "PlayScene.h"
|
||||
#include "audio_manager.h"
|
||||
#include "data.h"
|
||||
#include "PlayScene.h"
|
||||
#include <extra2d/extra2d.h>
|
||||
|
||||
namespace pushbox {
|
||||
|
||||
StartScene::StartScene() {
|
||||
auto& app = extra2d::Application::instance();
|
||||
auto& config = app.getConfig();
|
||||
setViewportSize(static_cast<float>(config.width), static_cast<float>(config.height));
|
||||
StartScene::StartScene() : BaseScene() {
|
||||
// BaseScene 已处理视口设置
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 加载菜单字体
|
||||
*/
|
||||
static extra2d::Ptr<extra2d::FontAtlas> loadMenuFont() {
|
||||
auto &resources = extra2d::Application::instance().resources();
|
||||
auto font = resources.loadFont("assets/font.ttf", 28, true);
|
||||
|
|
@ -20,16 +25,15 @@ static extra2d::Ptr<extra2d::FontAtlas> loadMenuFont() {
|
|||
}
|
||||
|
||||
void StartScene::onEnter() {
|
||||
Scene::onEnter();
|
||||
BaseScene::onEnter();
|
||||
|
||||
auto &app = extra2d::Application::instance();
|
||||
auto &resources = app.resources();
|
||||
setBackgroundColor(extra2d::Colors::Black);
|
||||
|
||||
if (getChildren().empty()) {
|
||||
|
||||
float screenW = static_cast<float>(app.getConfig().width);
|
||||
float screenH = static_cast<float>(app.getConfig().height);
|
||||
// 使用游戏逻辑分辨率
|
||||
float screenW = GAME_WIDTH;
|
||||
float screenH = GAME_HEIGHT;
|
||||
|
||||
auto bgTex = resources.loadTexture("assets/images/start.jpg");
|
||||
if (bgTex) {
|
||||
|
|
@ -51,7 +55,6 @@ void StartScene::onEnter() {
|
|||
}
|
||||
|
||||
// 创建菜单按钮(使用 Button 实现文本居中)
|
||||
// 设置按钮锚点为中心点,位置设为屏幕中心,实现真正的居中
|
||||
startBtn_ = extra2d::Button::create();
|
||||
startBtn_->setFont(font_);
|
||||
startBtn_->setText("新游戏");
|
||||
|
|
@ -94,13 +97,21 @@ void StartScene::onEnter() {
|
|||
exitBtn_->setPosition(centerX, offsetY + 340.0f);
|
||||
addChild(exitBtn_);
|
||||
|
||||
// 音效开关图标(相对于背景图左上角)
|
||||
// 音效开关按钮(使用 Button 的切换模式)
|
||||
auto soundOn = resources.loadTexture("assets/images/soundon.png");
|
||||
auto soundOff = resources.loadTexture("assets/images/soundoff.png");
|
||||
if (soundOn && soundOff) {
|
||||
soundIcon_ = extra2d::Sprite::create(g_SoundOpen ? soundOn : soundOff);
|
||||
soundIcon_->setPosition(offsetX + 50.0f, offsetY + 50.0f);
|
||||
addChild(soundIcon_);
|
||||
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_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -111,7 +122,7 @@ void StartScene::onEnter() {
|
|||
}
|
||||
|
||||
void StartScene::onUpdate(float dt) {
|
||||
Scene::onUpdate(dt);
|
||||
BaseScene::onUpdate(dt);
|
||||
|
||||
auto &app = extra2d::Application::instance();
|
||||
auto &input = app.input();
|
||||
|
|
@ -130,27 +141,34 @@ void StartScene::onUpdate(float dt) {
|
|||
executeMenuItem();
|
||||
}
|
||||
|
||||
// X键切换音效
|
||||
// X键切换音效(备用,按钮也可点击切换)
|
||||
if (input.isButtonPressed(extra2d::GamepadButton::X)) {
|
||||
g_SoundOpen = !g_SoundOpen;
|
||||
AudioManager::instance().setEnabled(g_SoundOpen);
|
||||
updateSoundIcon();
|
||||
if (soundBtn_) {
|
||||
soundBtn_->setOn(g_SoundOpen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新菜单颜色
|
||||
*/
|
||||
void StartScene::updateMenuColors() {
|
||||
// 根据选中状态更新按钮文本颜色
|
||||
// 选中的项用红色,未选中的用黑色,禁用的项用深灰色
|
||||
|
||||
if (startBtn_) {
|
||||
startBtn_->setTextColor(selectedIndex_ == 0 ? extra2d::Colors::Red : extra2d::Colors::Black);
|
||||
startBtn_->setTextColor(selectedIndex_ == 0 ? extra2d::Colors::Red
|
||||
: extra2d::Colors::Black);
|
||||
}
|
||||
|
||||
if (resumeBtn_) {
|
||||
// "继续关卡"始终显示,但当 g_CurrentLevel == 1 时禁用(深灰色)
|
||||
if (g_CurrentLevel > 1) {
|
||||
// 可用状态:选中为红色,未选中为黑色
|
||||
resumeBtn_->setTextColor(selectedIndex_ == 1 ? extra2d::Colors::Red : extra2d::Colors::Black);
|
||||
resumeBtn_->setTextColor(selectedIndex_ == 1 ? extra2d::Colors::Red
|
||||
: extra2d::Colors::Black);
|
||||
} else {
|
||||
// 禁用状态:深灰色 (RGB: 80, 80, 80)
|
||||
resumeBtn_->setTextColor(extra2d::Color(80, 80, 80, 255));
|
||||
|
|
@ -158,23 +176,14 @@ void StartScene::updateMenuColors() {
|
|||
}
|
||||
|
||||
if (exitBtn_) {
|
||||
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);
|
||||
exitBtn_->setTextColor(selectedIndex_ == 2 ? extra2d::Colors::Red
|
||||
: extra2d::Colors::Black);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 执行选中的菜单项
|
||||
*/
|
||||
void StartScene::executeMenuItem() {
|
||||
// 始终有3个选项,但"继续关卡"(索引1)在 g_CurrentLevel == 1 时禁用
|
||||
switch (selectedIndex_) {
|
||||
|
|
@ -193,18 +202,26 @@ void StartScene::executeMenuItem() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 开始新游戏
|
||||
*/
|
||||
void StartScene::startNewGame() {
|
||||
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() {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,28 +1,66 @@
|
|||
// ============================================================================
|
||||
// StartScene.h - Push Box 开始场景
|
||||
// ============================================================================
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BaseScene.h"
|
||||
#include <extra2d/extra2d.h>
|
||||
|
||||
namespace pushbox {
|
||||
|
||||
class StartScene : public extra2d::Scene {
|
||||
/**
|
||||
* @brief Push Box 开始场景(主菜单)
|
||||
*/
|
||||
class StartScene : public BaseScene {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
*/
|
||||
StartScene();
|
||||
|
||||
/**
|
||||
* @brief 场景进入时调用
|
||||
*/
|
||||
void onEnter() override;
|
||||
|
||||
/**
|
||||
* @brief 每帧更新
|
||||
* @param dt 帧间隔时间
|
||||
*/
|
||||
void onUpdate(float dt) override;
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 更新菜单颜色
|
||||
*/
|
||||
void updateMenuColors();
|
||||
void updateSoundIcon();
|
||||
|
||||
/**
|
||||
* @brief 执行选中的菜单项
|
||||
*/
|
||||
void executeMenuItem();
|
||||
|
||||
/**
|
||||
* @brief 开始新游戏
|
||||
*/
|
||||
void startNewGame();
|
||||
|
||||
/**
|
||||
* @brief 继续游戏
|
||||
*/
|
||||
void continueGame();
|
||||
|
||||
/**
|
||||
* @brief 退出游戏
|
||||
*/
|
||||
void exitGame();
|
||||
|
||||
extra2d::Ptr<extra2d::FontAtlas> font_;
|
||||
extra2d::Ptr<extra2d::Button> startBtn_;
|
||||
extra2d::Ptr<extra2d::Button> resumeBtn_;
|
||||
extra2d::Ptr<extra2d::Button> exitBtn_;
|
||||
extra2d::Ptr<extra2d::Sprite> soundIcon_;
|
||||
extra2d::Ptr<extra2d::Button> soundBtn_;
|
||||
int selectedIndex_ = 0;
|
||||
int menuCount_ = 3;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,15 +1,20 @@
|
|||
// ============================================================================
|
||||
// SuccessScene.cpp - Push Box 通关场景实现
|
||||
// ============================================================================
|
||||
|
||||
#include "SuccessScene.h"
|
||||
|
||||
#include <extra2d/extra2d.h>
|
||||
|
||||
namespace pushbox {
|
||||
|
||||
SuccessScene::SuccessScene() {
|
||||
auto& app = extra2d::Application::instance();
|
||||
auto& config = app.getConfig();
|
||||
setViewportSize(static_cast<float>(config.width), static_cast<float>(config.height));
|
||||
SuccessScene::SuccessScene() : BaseScene() {
|
||||
// BaseScene 已处理视口设置
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 加载菜单字体
|
||||
*/
|
||||
static extra2d::Ptr<extra2d::FontAtlas> loadMenuFont() {
|
||||
auto& resources = extra2d::Application::instance().resources();
|
||||
auto font = resources.loadFont("assets/font.ttf", 28);
|
||||
|
|
@ -17,16 +22,15 @@ static extra2d::Ptr<extra2d::FontAtlas> loadMenuFont() {
|
|||
}
|
||||
|
||||
void SuccessScene::onEnter() {
|
||||
Scene::onEnter();
|
||||
BaseScene::onEnter();
|
||||
|
||||
auto& app = extra2d::Application::instance();
|
||||
auto& resources = app.resources();
|
||||
setBackgroundColor(extra2d::Colors::Black);
|
||||
|
||||
if (getChildren().empty()) {
|
||||
// 获取窗口尺寸
|
||||
float screenW = static_cast<float>(app.getConfig().width);
|
||||
float screenH = static_cast<float>(app.getConfig().height);
|
||||
// 使用游戏逻辑分辨率
|
||||
float screenW = GAME_WIDTH;
|
||||
float screenH = GAME_HEIGHT;
|
||||
|
||||
auto bgTex = resources.loadTexture("assets/images/success.jpg");
|
||||
if (bgTex) {
|
||||
|
|
@ -61,7 +65,7 @@ void SuccessScene::onEnter() {
|
|||
}
|
||||
|
||||
void SuccessScene::onUpdate(float dt) {
|
||||
Scene::onUpdate(dt);
|
||||
BaseScene::onUpdate(dt);
|
||||
|
||||
auto& app = extra2d::Application::instance();
|
||||
auto& input = app.input();
|
||||
|
|
@ -69,8 +73,8 @@ void SuccessScene::onUpdate(float dt) {
|
|||
// A键确认返回主菜单
|
||||
if (input.isButtonPressed(extra2d::GamepadButton::A)) {
|
||||
auto& scenes = extra2d::Application::instance().scenes();
|
||||
scenes.popScene(extra2d::TransitionType::Fade, 0.2f);
|
||||
scenes.popScene(extra2d::TransitionType::Fade, 0.2f);
|
||||
scenes.popScene(extra2d::TransitionType::Fade, 0.5f);
|
||||
scenes.popScene(extra2d::TransitionType::Fade, 0.5f);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,33 @@
|
|||
// ============================================================================
|
||||
// SuccessScene.h - Push Box 通关场景
|
||||
// ============================================================================
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BaseScene.h"
|
||||
#include <extra2d/extra2d.h>
|
||||
|
||||
namespace pushbox {
|
||||
|
||||
class SuccessScene : public extra2d::Scene {
|
||||
/**
|
||||
* @brief Push Box 通关场景
|
||||
*/
|
||||
class SuccessScene : public BaseScene {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
*/
|
||||
SuccessScene();
|
||||
|
||||
/**
|
||||
* @brief 场景进入时调用
|
||||
*/
|
||||
void onEnter() override;
|
||||
|
||||
/**
|
||||
* @brief 每帧更新
|
||||
* @param dt 帧间隔时间
|
||||
*/
|
||||
void onUpdate(float dt) override;
|
||||
|
||||
private:
|
||||
|
|
|
|||
Loading…
Reference in New Issue