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);
|
||||||
void enterScene(Ptr<class Scene> scene, Ptr<class Transition> transition);
|
void enterScene(Ptr<class Scene> scene, Ptr<class TransitionScene> transitionScene);
|
||||||
|
|
||||||
float deltaTime() const { return deltaTime_; }
|
float deltaTime() const { return deltaTime_; }
|
||||||
float totalTime() const { return totalTime_; }
|
float totalTime() const { return totalTime_; }
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,12 @@
|
||||||
#include <extra2d/scene/sprite.h>
|
#include <extra2d/scene/sprite.h>
|
||||||
#include <extra2d/scene/shape_node.h>
|
#include <extra2d/scene/shape_node.h>
|
||||||
#include <extra2d/scene/scene_manager.h>
|
#include <extra2d/scene/scene_manager.h>
|
||||||
#include <extra2d/scene/transition.h>
|
#include <extra2d/scene/transition_scene.h>
|
||||||
|
#include <extra2d/scene/transition_fade_scene.h>
|
||||||
|
#include <extra2d/scene/transition_slide_scene.h>
|
||||||
|
#include <extra2d/scene/transition_scale_scene.h>
|
||||||
|
#include <extra2d/scene/transition_flip_scene.h>
|
||||||
|
#include <extra2d/scene/transition_box_scene.h>
|
||||||
|
|
||||||
// Animation
|
// Animation
|
||||||
#include <extra2d/animation/sprite_frame.h>
|
#include <extra2d/animation/sprite_frame.h>
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ public:
|
||||||
// 渲染和更新
|
// 渲染和更新
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
void renderScene(RenderBackend &renderer);
|
void renderScene(RenderBackend &renderer);
|
||||||
void renderContent(RenderBackend &renderer);
|
virtual void renderContent(RenderBackend &renderer);
|
||||||
void updateScene(float dt);
|
void updateScene(float dt);
|
||||||
void collectRenderCommands(std::vector<RenderCommand> &commands,
|
void collectRenderCommands(std::vector<RenderCommand> &commands,
|
||||||
int parentZOrder = 0) override;
|
int parentZOrder = 0) override;
|
||||||
|
|
@ -92,7 +92,12 @@ protected:
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
|
|
||||||
|
// 过渡场景生命周期回调(供 TransitionScene 使用)
|
||||||
|
virtual void onExitTransitionDidStart() {}
|
||||||
|
virtual void onEnterTransitionDidFinish() {}
|
||||||
|
|
||||||
friend class SceneManager;
|
friend class SceneManager;
|
||||||
|
friend class TransitionScene;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Color backgroundColor_ = Colors::Black;
|
Color backgroundColor_ = Colors::Black;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
#include <extra2d/scene/scene.h>
|
#include <extra2d/scene/scene.h>
|
||||||
|
#include <extra2d/scene/transition_scene.h>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <stack>
|
#include <stack>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
@ -12,21 +13,7 @@ namespace extra2d {
|
||||||
|
|
||||||
// 前向声明
|
// 前向声明
|
||||||
struct RenderCommand;
|
struct RenderCommand;
|
||||||
class Transition;
|
class TransitionScene;
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 场景切换特效类型
|
|
||||||
// ============================================================================
|
|
||||||
enum class TransitionType {
|
|
||||||
None,
|
|
||||||
Fade,
|
|
||||||
SlideLeft,
|
|
||||||
SlideRight,
|
|
||||||
SlideUp,
|
|
||||||
SlideDown,
|
|
||||||
Scale,
|
|
||||||
Flip
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 场景管理器 - 管理场景的生命周期和切换
|
// 场景管理器 - 管理场景的生命周期和切换
|
||||||
|
|
@ -116,27 +103,27 @@ public:
|
||||||
|
|
||||||
// 场景切换(供 Application 使用)
|
// 场景切换(供 Application 使用)
|
||||||
void enterScene(Ptr<Scene> scene);
|
void enterScene(Ptr<Scene> scene);
|
||||||
void enterScene(Ptr<Scene> scene, Ptr<class Transition> transition);
|
void enterScene(Ptr<Scene> scene, Ptr<TransitionScene> transitionScene);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void doSceneSwitch();
|
void doSceneSwitch();
|
||||||
void startTransition(Ptr<Scene> from, Ptr<Scene> to, TransitionType type,
|
void startTransition(Ptr<Scene> from, Ptr<Scene> to, TransitionType type,
|
||||||
float duration, Function<void()> stackAction);
|
float duration, Function<void()> stackAction);
|
||||||
void updateTransition(float dt);
|
|
||||||
void finishTransition();
|
void finishTransition();
|
||||||
void dispatchPointerEvents(Scene &scene);
|
void dispatchPointerEvents(Scene &scene);
|
||||||
|
|
||||||
|
// 创建过渡场景
|
||||||
|
Ptr<TransitionScene> createTransitionScene(TransitionType type,
|
||||||
|
float duration,
|
||||||
|
Ptr<Scene> inScene);
|
||||||
|
|
||||||
std::stack<Ptr<Scene>> sceneStack_;
|
std::stack<Ptr<Scene>> sceneStack_;
|
||||||
std::unordered_map<std::string, Ptr<Scene>> namedScenes_;
|
std::unordered_map<std::string, Ptr<Scene>> namedScenes_;
|
||||||
|
|
||||||
// Transition state
|
// Transition state
|
||||||
bool isTransitioning_ = false;
|
bool isTransitioning_ = false;
|
||||||
TransitionType currentTransition_ = TransitionType::None;
|
TransitionType currentTransition_ = TransitionType::None;
|
||||||
float transitionDuration_ = 0.0f;
|
Ptr<TransitionScene> activeTransitionScene_;
|
||||||
float transitionElapsed_ = 0.0f;
|
|
||||||
Ptr<Scene> outgoingScene_;
|
|
||||||
Ptr<Scene> incomingScene_;
|
|
||||||
Ptr<Transition> activeTransition_;
|
|
||||||
Function<void()> transitionStackAction_;
|
Function<void()> transitionStackAction_;
|
||||||
TransitionCallback transitionCallback_;
|
TransitionCallback transitionCallback_;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
void setBorder(const Color &color, float width);
|
||||||
|
float getBorderWidth() const { return borderWidth_; }
|
||||||
|
Color getBorderColor() const { return borderColor_; }
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 图片背景设置
|
// 图片背景设置
|
||||||
|
|
@ -102,6 +104,22 @@ public:
|
||||||
void setBackgroundImage(Ptr<Texture> normal, Ptr<Texture> hover = nullptr,
|
void setBackgroundImage(Ptr<Texture> normal, Ptr<Texture> hover = nullptr,
|
||||||
Ptr<Texture> pressed = nullptr);
|
Ptr<Texture> pressed = nullptr);
|
||||||
void setBackgroundImage(Ptr<Texture> texture, const Rect &rect);
|
void setBackgroundImage(Ptr<Texture> texture, const Rect &rect);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 为切换按钮的两种状态设置图片背景
|
||||||
|
* @param offNormal 关闭状态的普通图片
|
||||||
|
* @param onNormal 开启状态的普通图片
|
||||||
|
* @param offHover 关闭状态的悬停图片(可选)
|
||||||
|
* @param onHover 开启状态的悬停图片(可选)
|
||||||
|
* @param offPressed 关闭状态的按下图片(可选)
|
||||||
|
* @param onPressed 开启状态的按下图片(可选)
|
||||||
|
*/
|
||||||
|
void setStateBackgroundImage(Ptr<Texture> offNormal, Ptr<Texture> onNormal,
|
||||||
|
Ptr<Texture> offHover = nullptr,
|
||||||
|
Ptr<Texture> onHover = nullptr,
|
||||||
|
Ptr<Texture> offPressed = nullptr,
|
||||||
|
Ptr<Texture> onPressed = nullptr);
|
||||||
|
|
||||||
void setBackgroundImageScaleMode(ImageScaleMode mode);
|
void setBackgroundImageScaleMode(ImageScaleMode mode);
|
||||||
void setCustomSize(const Vec2 &size);
|
void setCustomSize(const Vec2 &size);
|
||||||
void setCustomSize(float width, float height);
|
void setCustomSize(float width, float height);
|
||||||
|
|
@ -131,6 +149,63 @@ public:
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
void setOnClick(Function<void()> callback);
|
void setOnClick(Function<void()> callback);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 切换模式支持(Toggle Button)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置是否为切换模式
|
||||||
|
* @param enabled true 表示启用切换模式,点击时自动切换 on/off 状态
|
||||||
|
*/
|
||||||
|
void setToggleMode(bool enabled);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前是否为切换模式
|
||||||
|
* @return true 表示处于切换模式
|
||||||
|
*/
|
||||||
|
bool isToggleMode() const { return toggleMode_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置当前状态(仅切换模式有效)
|
||||||
|
* @param on true 表示开启状态,false 表示关闭状态
|
||||||
|
*/
|
||||||
|
void setOn(bool on);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前状态
|
||||||
|
* @return true 表示开启状态,false 表示关闭状态
|
||||||
|
*/
|
||||||
|
bool isOn() const { return isOn_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 切换当前状态
|
||||||
|
*/
|
||||||
|
void toggle();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置状态改变回调
|
||||||
|
* @param callback 状态改变时调用的回调函数,参数为新状态
|
||||||
|
*/
|
||||||
|
void setOnStateChange(Function<void(bool)> callback);
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// 状态文字设置(用于切换按钮)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 为两种状态设置不同的文字
|
||||||
|
* @param textOff 关闭状态显示的文字
|
||||||
|
* @param textOn 开启状态显示的文字
|
||||||
|
*/
|
||||||
|
void setStateText(const std::string &textOff, const std::string &textOn);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 为两种状态设置不同的文字颜色
|
||||||
|
* @param colorOff 关闭状态的文字颜色
|
||||||
|
* @param colorOn 开启状态的文字颜色
|
||||||
|
*/
|
||||||
|
void setStateTextColor(const Color &colorOff, const Color &colorOn);
|
||||||
|
|
||||||
Rect getBoundingBox() const override;
|
Rect getBoundingBox() const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
@ -170,6 +245,12 @@ private:
|
||||||
bool useImageBackground_ = false;
|
bool useImageBackground_ = false;
|
||||||
bool useTextureRect_ = false;
|
bool useTextureRect_ = false;
|
||||||
|
|
||||||
|
// 切换按钮状态图片
|
||||||
|
Ptr<Texture> imgOffNormal_, imgOnNormal_;
|
||||||
|
Ptr<Texture> imgOffHover_, imgOnHover_;
|
||||||
|
Ptr<Texture> imgOffPressed_, imgOnPressed_;
|
||||||
|
bool useStateImages_ = false;
|
||||||
|
|
||||||
// 边框
|
// 边框
|
||||||
Color borderColor_ = Color(0.6f, 0.6f, 0.6f, 1.0f);
|
Color borderColor_ = Color(0.6f, 0.6f, 0.6f, 1.0f);
|
||||||
float borderWidth_ = 1.0f;
|
float borderWidth_ = 1.0f;
|
||||||
|
|
@ -188,48 +269,10 @@ private:
|
||||||
bool hovered_ = false;
|
bool hovered_ = false;
|
||||||
bool pressed_ = false;
|
bool pressed_ = false;
|
||||||
|
|
||||||
Function<void()> onClick_;
|
// 切换模式相关
|
||||||
};
|
bool toggleMode_ = false;
|
||||||
|
bool isOn_ = false;
|
||||||
// ============================================================================
|
Function<void(bool)> onStateChange_;
|
||||||
// 切换按钮 - 点击切换两种状态(支持图片和文字)
|
|
||||||
// ============================================================================
|
|
||||||
class ToggleImageButton : public Button {
|
|
||||||
public:
|
|
||||||
ToggleImageButton();
|
|
||||||
~ToggleImageButton() override = default;
|
|
||||||
|
|
||||||
static Ptr<ToggleImageButton> create();
|
|
||||||
|
|
||||||
// 设置两种状态的图片
|
|
||||||
void setStateImages(Ptr<Texture> stateOffNormal, Ptr<Texture> stateOnNormal,
|
|
||||||
Ptr<Texture> stateOffHover = nullptr,
|
|
||||||
Ptr<Texture> stateOnHover = nullptr,
|
|
||||||
Ptr<Texture> stateOffPressed = nullptr,
|
|
||||||
Ptr<Texture> stateOnPressed = nullptr);
|
|
||||||
|
|
||||||
// 设置两种状态的文字
|
|
||||||
void setStateText(const std::string &textOff, const std::string &textOn);
|
|
||||||
|
|
||||||
// 设置两种状态的文字颜色
|
|
||||||
void setStateTextColor(const Color &colorOff, const Color &colorOn);
|
|
||||||
|
|
||||||
// 获取/设置当前状态
|
|
||||||
bool isOn() const { return isOn_; }
|
|
||||||
void setOn(bool on);
|
|
||||||
void toggle();
|
|
||||||
|
|
||||||
// 设置状态改变回调
|
|
||||||
void setOnStateChange(Function<void(bool)> callback);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void onDrawWidget(RenderBackend &renderer) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
// 状态图片
|
|
||||||
Ptr<Texture> imgOffNormal_, imgOnNormal_;
|
|
||||||
Ptr<Texture> imgOffHover_, imgOnHover_;
|
|
||||||
Ptr<Texture> imgOffPressed_, imgOnPressed_;
|
|
||||||
|
|
||||||
// 状态文字
|
// 状态文字
|
||||||
std::string textOff_, textOn_;
|
std::string textOff_, textOn_;
|
||||||
|
|
@ -240,8 +283,7 @@ private:
|
||||||
Color textColorOn_ = Colors::White;
|
Color textColorOn_ = Colors::White;
|
||||||
bool useStateTextColor_ = false;
|
bool useStateTextColor_ = false;
|
||||||
|
|
||||||
bool isOn_ = false;
|
Function<void()> onClick_;
|
||||||
Function<void(bool)> onStateChange_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -364,11 +364,11 @@ Camera &Application::camera() { return *camera_; }
|
||||||
void Application::enterScene(Ptr<Scene> scene) { enterScene(scene, nullptr); }
|
void Application::enterScene(Ptr<Scene> scene) { enterScene(scene, nullptr); }
|
||||||
|
|
||||||
void Application::enterScene(Ptr<Scene> scene,
|
void Application::enterScene(Ptr<Scene> scene,
|
||||||
Ptr<class Transition> transition) {
|
Ptr<class TransitionScene> transitionScene) {
|
||||||
if (sceneManager_ && scene) {
|
if (sceneManager_ && scene) {
|
||||||
scene->setViewportSize(static_cast<float>(window_->getWidth()),
|
scene->setViewportSize(static_cast<float>(window_->getWidth()),
|
||||||
static_cast<float>(window_->getHeight()));
|
static_cast<float>(window_->getHeight()));
|
||||||
sceneManager_->enterScene(scene, transition);
|
sceneManager_->enterScene(scene, transitionScene);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,57 +4,16 @@
|
||||||
#include <extra2d/graphics/render_command.h>
|
#include <extra2d/graphics/render_command.h>
|
||||||
#include <extra2d/platform/input.h>
|
#include <extra2d/platform/input.h>
|
||||||
#include <extra2d/scene/scene_manager.h>
|
#include <extra2d/scene/scene_manager.h>
|
||||||
#include <extra2d/scene/transition.h>
|
#include <extra2d/scene/transition_box_scene.h>
|
||||||
|
#include <extra2d/scene/transition_fade_scene.h>
|
||||||
|
#include <extra2d/scene/transition_flip_scene.h>
|
||||||
|
#include <extra2d/scene/transition_scale_scene.h>
|
||||||
|
#include <extra2d/scene/transition_scene.h>
|
||||||
|
#include <extra2d/scene/transition_slide_scene.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Transition 工厂映射 - 使用函数指针数组替代 switch
|
|
||||||
// ============================================================================
|
|
||||||
using TransitionFactory = Ptr<Transition> (*)(float);
|
|
||||||
|
|
||||||
static Ptr<Transition> createFadeTransition(float duration) {
|
|
||||||
return makePtr<FadeTransition>(duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Ptr<Transition> createSlideLeftTransition(float duration) {
|
|
||||||
return makePtr<SlideTransition>(duration, TransitionDirection::Left);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Ptr<Transition> createSlideRightTransition(float duration) {
|
|
||||||
return makePtr<SlideTransition>(duration, TransitionDirection::Right);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Ptr<Transition> createSlideUpTransition(float duration) {
|
|
||||||
return makePtr<SlideTransition>(duration, TransitionDirection::Up);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Ptr<Transition> createSlideDownTransition(float duration) {
|
|
||||||
return makePtr<SlideTransition>(duration, TransitionDirection::Down);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Ptr<Transition> createScaleTransition(float duration) {
|
|
||||||
return makePtr<ScaleTransition>(duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Ptr<Transition> createFlipTransition(float duration) {
|
|
||||||
return makePtr<FlipTransition>(duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 工厂函数指针数组,索引对应 TransitionType 枚举值
|
|
||||||
static constexpr TransitionFactory TRANSITION_FACTORIES[] = {
|
|
||||||
createFadeTransition, // TransitionType::Fade = 0
|
|
||||||
createSlideLeftTransition, // TransitionType::SlideLeft = 1
|
|
||||||
createSlideRightTransition,// TransitionType::SlideRight = 2
|
|
||||||
createSlideUpTransition, // TransitionType::SlideUp = 3
|
|
||||||
createSlideDownTransition, // TransitionType::SlideDown = 4
|
|
||||||
createScaleTransition, // TransitionType::Scale = 5
|
|
||||||
createFlipTransition // TransitionType::Flip = 6
|
|
||||||
};
|
|
||||||
|
|
||||||
static constexpr size_t TRANSITION_FACTORY_COUNT = sizeof(TRANSITION_FACTORIES) / sizeof(TRANSITION_FACTORIES[0]);
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
Node *hitTestTopmost(const Ptr<Node> &node, const Vec2 &worldPos) {
|
Node *hitTestTopmost(const Ptr<Node> &node, const Vec2 &worldPos) {
|
||||||
|
|
@ -150,17 +109,22 @@ void SceneManager::enterScene(Ptr<Scene> scene) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::enterScene(Ptr<Scene> scene,
|
void SceneManager::enterScene(Ptr<Scene> scene,
|
||||||
Ptr<class Transition> transition) {
|
Ptr<TransitionScene> transitionScene) {
|
||||||
if (!scene || isTransitioning_) {
|
if (!scene || isTransitioning_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!transition) {
|
// 如果没有过渡场景,使用无过渡切换
|
||||||
|
if (!transitionScene) {
|
||||||
enterScene(scene);
|
enterScene(scene);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto current = getCurrentScene();
|
auto current = getCurrentScene();
|
||||||
|
if (!current) {
|
||||||
|
enterScene(scene);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 在过渡开始前,发送 UIHoverExit 给当前悬停的节点,重置按钮状态
|
// 在过渡开始前,发送 UIHoverExit 给当前悬停的节点,重置按钮状态
|
||||||
if (hoverTarget_) {
|
if (hoverTarget_) {
|
||||||
|
|
@ -173,32 +137,39 @@ void SceneManager::enterScene(Ptr<Scene> scene,
|
||||||
captureTarget_ = nullptr;
|
captureTarget_ = nullptr;
|
||||||
hasLastPointerWorld_ = false;
|
hasLastPointerWorld_ = false;
|
||||||
|
|
||||||
transition->start(current, scene);
|
// 设置过渡场景
|
||||||
outgoingScene_ = current;
|
transitionScene->setOutScene(current);
|
||||||
incomingScene_ = scene;
|
transitionScene->setFinishCallback([this]() { finishTransition(); });
|
||||||
activeTransition_ = transition;
|
|
||||||
currentTransition_ = TransitionType::None;
|
// 暂停当前场景
|
||||||
|
current->pause();
|
||||||
|
|
||||||
|
// 推入过渡场景(作为中介场景)
|
||||||
|
transitionScene->onEnter();
|
||||||
|
transitionScene->onAttachToScene(transitionScene.get());
|
||||||
|
sceneStack_.push(transitionScene);
|
||||||
|
|
||||||
isTransitioning_ = true;
|
isTransitioning_ = true;
|
||||||
transitionStackAction_ = [this]() {
|
activeTransitionScene_ = transitionScene;
|
||||||
|
transitionStackAction_ = [this, transitionScene]() {
|
||||||
// 退出旧场景
|
// 退出旧场景
|
||||||
auto outgoing = outgoingScene_;
|
auto outScene = transitionScene->getOutScene();
|
||||||
if (!sceneStack_.empty() && outgoing && sceneStack_.top() == outgoing) {
|
if (!sceneStack_.empty() && outScene) {
|
||||||
outgoing->onExit();
|
// 过渡场景已经在栈顶,弹出它
|
||||||
outgoing->onDetachFromScene();
|
if (sceneStack_.top().get() == transitionScene.get()) {
|
||||||
sceneStack_.pop();
|
sceneStack_.pop();
|
||||||
}
|
|
||||||
// 推入新场景并调用 onEnter
|
|
||||||
auto incoming = incomingScene_;
|
|
||||||
if (incoming) {
|
|
||||||
sceneStack_.push(incoming);
|
|
||||||
if (!incoming->isRunning()) {
|
|
||||||
incoming->onEnter();
|
|
||||||
incoming->onAttachToScene(incoming.get());
|
|
||||||
}
|
}
|
||||||
|
outScene->onExit();
|
||||||
|
outScene->onDetachFromScene();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 推入新场景
|
||||||
|
auto inScene = transitionScene->getInScene();
|
||||||
|
if (inScene) {
|
||||||
|
inScene->onAttachToScene(inScene.get());
|
||||||
|
sceneStack_.push(inScene);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// 注意:不在此处调用新场景的 onEnter,由 transitionStackAction_
|
|
||||||
// 在过渡完成后调用
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::replaceScene(Ptr<Scene> scene, TransitionType transition,
|
void SceneManager::replaceScene(Ptr<Scene> scene, TransitionType transition,
|
||||||
|
|
@ -216,19 +187,26 @@ void SceneManager::replaceScene(Ptr<Scene> scene, TransitionType transition,
|
||||||
|
|
||||||
startTransition(oldScene, scene, transition, duration, [this]() {
|
startTransition(oldScene, scene, transition, duration, [this]() {
|
||||||
// 过渡完成后,退出旧场景并从堆栈中移除
|
// 过渡完成后,退出旧场景并从堆栈中移除
|
||||||
auto outgoing = outgoingScene_;
|
if (!sceneStack_.empty() && activeTransitionScene_) {
|
||||||
auto incoming = incomingScene_;
|
// 弹出过渡场景
|
||||||
if (!sceneStack_.empty() && outgoing && sceneStack_.top() == outgoing) {
|
if (sceneStack_.top().get() == activeTransitionScene_.get()) {
|
||||||
outgoing->onExit();
|
sceneStack_.pop();
|
||||||
outgoing->onDetachFromScene();
|
}
|
||||||
sceneStack_.pop();
|
|
||||||
|
// 退出旧场景
|
||||||
|
auto outScene = activeTransitionScene_->getOutScene();
|
||||||
|
if (outScene) {
|
||||||
|
outScene->onExit();
|
||||||
|
outScene->onDetachFromScene();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// 将新场景推入堆栈并调用 onEnter
|
|
||||||
if (incoming) {
|
// 将新场景推入堆栈
|
||||||
sceneStack_.push(incoming);
|
if (activeTransitionScene_) {
|
||||||
if (!incoming->isRunning()) {
|
auto inScene = activeTransitionScene_->getInScene();
|
||||||
incoming->onEnter();
|
if (inScene) {
|
||||||
incoming->onAttachToScene(incoming.get());
|
inScene->onAttachToScene(inScene.get());
|
||||||
|
sceneStack_.push(inScene);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -267,12 +245,19 @@ void SceneManager::pushScene(Ptr<Scene> scene, TransitionType transition,
|
||||||
auto currentScene = sceneStack_.top();
|
auto currentScene = sceneStack_.top();
|
||||||
|
|
||||||
startTransition(currentScene, scene, transition, duration, [this]() {
|
startTransition(currentScene, scene, transition, duration, [this]() {
|
||||||
// 过渡完成后,将新场景推入堆栈并调用 onEnter
|
// 过渡完成后,将新场景推入堆栈
|
||||||
if (incomingScene_) {
|
if (!sceneStack_.empty() && activeTransitionScene_) {
|
||||||
sceneStack_.push(incomingScene_);
|
// 弹出过渡场景
|
||||||
if (!incomingScene_->isRunning()) {
|
if (sceneStack_.top().get() == activeTransitionScene_.get()) {
|
||||||
incomingScene_->onEnter();
|
sceneStack_.pop();
|
||||||
incomingScene_->onAttachToScene(incomingScene_.get());
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeTransitionScene_) {
|
||||||
|
auto inScene = activeTransitionScene_->getInScene();
|
||||||
|
if (inScene) {
|
||||||
|
inScene->onAttachToScene(inScene.get());
|
||||||
|
sceneStack_.push(inScene);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -304,20 +289,26 @@ void SceneManager::popScene(TransitionType transition, float duration) {
|
||||||
|
|
||||||
startTransition(current, previous, transition, duration, [this]() {
|
startTransition(current, previous, transition, duration, [this]() {
|
||||||
// 过渡完成后,退出当前场景并从堆栈中移除
|
// 过渡完成后,退出当前场景并从堆栈中移除
|
||||||
auto outgoing = outgoingScene_;
|
if (!sceneStack_.empty() && activeTransitionScene_) {
|
||||||
if (!sceneStack_.empty() && outgoing && sceneStack_.top() == outgoing) {
|
// 弹出过渡场景
|
||||||
outgoing->onExit();
|
if (sceneStack_.top().get() == activeTransitionScene_.get()) {
|
||||||
outgoing->onDetachFromScene();
|
sceneStack_.pop();
|
||||||
sceneStack_.pop();
|
}
|
||||||
}
|
|
||||||
// 恢复前一个场景
|
// 退出当前场景
|
||||||
auto incoming = incomingScene_;
|
auto outScene = activeTransitionScene_->getOutScene();
|
||||||
if (!sceneStack_.empty() && incoming && sceneStack_.top() == incoming) {
|
if (outScene) {
|
||||||
if (!incoming->isRunning()) {
|
outScene->onExit();
|
||||||
incoming->onEnter();
|
outScene->onDetachFromScene();
|
||||||
incoming->onAttachToScene(incoming.get());
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 恢复前一个场景
|
||||||
|
if (activeTransitionScene_) {
|
||||||
|
auto inScene = activeTransitionScene_->getInScene();
|
||||||
|
if (inScene && !sceneStack_.empty() && sceneStack_.top() == inScene) {
|
||||||
|
inScene->resume();
|
||||||
}
|
}
|
||||||
incoming->resume();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -349,18 +340,15 @@ void SceneManager::popToRootScene(TransitionType transition, float duration) {
|
||||||
|
|
||||||
startTransition(current, root, transition, duration, [this, root]() {
|
startTransition(current, root, transition, duration, [this, root]() {
|
||||||
// 退出所有场景直到根场景
|
// 退出所有场景直到根场景
|
||||||
while (!sceneStack_.empty() && sceneStack_.top() != root) {
|
while (!sceneStack_.empty() && sceneStack_.top().get() != root.get()) {
|
||||||
auto scene = sceneStack_.top();
|
auto scene = sceneStack_.top();
|
||||||
scene->onExit();
|
scene->onExit();
|
||||||
scene->onDetachFromScene();
|
scene->onDetachFromScene();
|
||||||
sceneStack_.pop();
|
sceneStack_.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 恢复根场景
|
// 恢复根场景
|
||||||
if (!sceneStack_.empty() && sceneStack_.top() == root) {
|
if (!sceneStack_.empty() && sceneStack_.top().get() == root.get()) {
|
||||||
if (!root->isRunning()) {
|
|
||||||
root->onEnter();
|
|
||||||
root->onAttachToScene(root.get());
|
|
||||||
}
|
|
||||||
root->resume();
|
root->resume();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -409,12 +397,9 @@ void SceneManager::popToScene(const std::string &name,
|
||||||
scene->onDetachFromScene();
|
scene->onDetachFromScene();
|
||||||
sceneStack_.pop();
|
sceneStack_.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 恢复目标场景
|
// 恢复目标场景
|
||||||
if (!sceneStack_.empty() && sceneStack_.top() == target) {
|
if (!sceneStack_.empty() && sceneStack_.top() == target) {
|
||||||
if (!target->isRunning()) {
|
|
||||||
target->onEnter();
|
|
||||||
target->onAttachToScene(target.get());
|
|
||||||
}
|
|
||||||
target->resume();
|
target->resume();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -479,7 +464,7 @@ bool SceneManager::hasScene(const std::string &name) const {
|
||||||
|
|
||||||
void SceneManager::update(float dt) {
|
void SceneManager::update(float dt) {
|
||||||
if (isTransitioning_) {
|
if (isTransitioning_) {
|
||||||
updateTransition(dt);
|
// 过渡场景在栈顶,正常更新即可
|
||||||
hoverTarget_ = nullptr;
|
hoverTarget_ = nullptr;
|
||||||
captureTarget_ = nullptr;
|
captureTarget_ = nullptr;
|
||||||
hasLastPointerWorld_ = false;
|
hasLastPointerWorld_ = false;
|
||||||
|
|
@ -496,13 +481,7 @@ void SceneManager::update(float dt) {
|
||||||
|
|
||||||
void SceneManager::render(RenderBackend &renderer) {
|
void SceneManager::render(RenderBackend &renderer) {
|
||||||
Color clearColor = Colors::Black;
|
Color clearColor = Colors::Black;
|
||||||
if (isTransitioning_) {
|
if (!sceneStack_.empty()) {
|
||||||
if (outgoingScene_) {
|
|
||||||
clearColor = outgoingScene_->getBackgroundColor();
|
|
||||||
} else if (incomingScene_) {
|
|
||||||
clearColor = incomingScene_->getBackgroundColor();
|
|
||||||
}
|
|
||||||
} else if (!sceneStack_.empty()) {
|
|
||||||
clearColor = sceneStack_.top()->getBackgroundColor();
|
clearColor = sceneStack_.top()->getBackgroundColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -510,10 +489,7 @@ void SceneManager::render(RenderBackend &renderer) {
|
||||||
clearColor.r, clearColor.g, clearColor.b);
|
clearColor.r, clearColor.g, clearColor.b);
|
||||||
renderer.beginFrame(clearColor);
|
renderer.beginFrame(clearColor);
|
||||||
|
|
||||||
if (isTransitioning_ && activeTransition_) {
|
if (!sceneStack_.empty()) {
|
||||||
E2D_LOG_TRACE("SceneManager::render - rendering transition");
|
|
||||||
activeTransition_->render(renderer);
|
|
||||||
} else if (!sceneStack_.empty()) {
|
|
||||||
E2D_LOG_TRACE("SceneManager::render - rendering scene content");
|
E2D_LOG_TRACE("SceneManager::render - rendering scene content");
|
||||||
sceneStack_.top()->renderContent(renderer);
|
sceneStack_.top()->renderContent(renderer);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -525,13 +501,7 @@ void SceneManager::render(RenderBackend &renderer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::collectRenderCommands(std::vector<RenderCommand> &commands) {
|
void SceneManager::collectRenderCommands(std::vector<RenderCommand> &commands) {
|
||||||
if (isTransitioning_ && outgoingScene_) {
|
if (!sceneStack_.empty()) {
|
||||||
// During transition, collect commands from both scenes
|
|
||||||
outgoingScene_->collectRenderCommands(commands, 0);
|
|
||||||
if (incomingScene_) {
|
|
||||||
incomingScene_->collectRenderCommands(commands, 0);
|
|
||||||
}
|
|
||||||
} else if (!sceneStack_.empty()) {
|
|
||||||
sceneStack_.top()->collectRenderCommands(commands, 0);
|
sceneStack_.top()->collectRenderCommands(commands, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -555,18 +525,14 @@ void SceneManager::startTransition(Ptr<Scene> from, Ptr<Scene> to,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用工厂映射替代 switch
|
// 创建过渡场景
|
||||||
Ptr<Transition> transition;
|
auto transitionScene = createTransitionScene(type, duration, to);
|
||||||
size_t typeIndex = static_cast<size_t>(type);
|
if (!transitionScene) {
|
||||||
if (typeIndex < TRANSITION_FACTORY_COUNT) {
|
// 回退到无过渡切换
|
||||||
transition = TRANSITION_FACTORIES[typeIndex](duration);
|
replaceScene(to);
|
||||||
} else {
|
return;
|
||||||
// 默认使用 Fade 过渡
|
|
||||||
transition = TRANSITION_FACTORIES[0](duration);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
transition->start(from, to);
|
|
||||||
|
|
||||||
// 在过渡开始前,发送 UIHoverExit 给当前悬停的节点,重置按钮状态
|
// 在过渡开始前,发送 UIHoverExit 给当前悬停的节点,重置按钮状态
|
||||||
if (hoverTarget_) {
|
if (hoverTarget_) {
|
||||||
Event evt;
|
Event evt;
|
||||||
|
|
@ -578,29 +544,55 @@ void SceneManager::startTransition(Ptr<Scene> from, Ptr<Scene> to,
|
||||||
captureTarget_ = nullptr;
|
captureTarget_ = nullptr;
|
||||||
hasLastPointerWorld_ = false;
|
hasLastPointerWorld_ = false;
|
||||||
|
|
||||||
|
// 设置过渡场景
|
||||||
|
transitionScene->setOutScene(from);
|
||||||
|
transitionScene->setFinishCallback([this]() { finishTransition(); });
|
||||||
|
|
||||||
|
// 暂停当前场景
|
||||||
|
from->pause();
|
||||||
|
|
||||||
|
// 推入过渡场景(作为中介场景)
|
||||||
|
transitionScene->onEnter();
|
||||||
|
transitionScene->onAttachToScene(transitionScene.get());
|
||||||
|
sceneStack_.push(transitionScene);
|
||||||
|
|
||||||
isTransitioning_ = true;
|
isTransitioning_ = true;
|
||||||
currentTransition_ = type;
|
currentTransition_ = type;
|
||||||
transitionDuration_ = duration;
|
activeTransitionScene_ = transitionScene;
|
||||||
transitionElapsed_ = 0.0f;
|
|
||||||
outgoingScene_ = from;
|
|
||||||
incomingScene_ = to;
|
|
||||||
activeTransition_ = transition;
|
|
||||||
transitionStackAction_ = std::move(stackAction);
|
transitionStackAction_ = std::move(stackAction);
|
||||||
|
|
||||||
// 注意:不在此处调用新场景的 onEnter,由 transitionStackAction_
|
|
||||||
// 在过渡完成后调用
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneManager::updateTransition(float dt) {
|
Ptr<TransitionScene> SceneManager::createTransitionScene(TransitionType type,
|
||||||
transitionElapsed_ += dt;
|
float duration,
|
||||||
|
Ptr<Scene> inScene) {
|
||||||
|
if (!inScene) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
if (activeTransition_) {
|
switch (type) {
|
||||||
activeTransition_->update(dt);
|
case TransitionType::Fade:
|
||||||
if (activeTransition_->isFinished()) {
|
return TransitionFadeScene::create(duration, inScene);
|
||||||
finishTransition();
|
case TransitionType::SlideLeft:
|
||||||
}
|
return TransitionSlideScene::create(duration, inScene,
|
||||||
} else {
|
TransitionDirection::Left);
|
||||||
finishTransition();
|
case TransitionType::SlideRight:
|
||||||
|
return TransitionSlideScene::create(duration, inScene,
|
||||||
|
TransitionDirection::Right);
|
||||||
|
case TransitionType::SlideUp:
|
||||||
|
return TransitionSlideScene::create(duration, inScene,
|
||||||
|
TransitionDirection::Up);
|
||||||
|
case TransitionType::SlideDown:
|
||||||
|
return TransitionSlideScene::create(duration, inScene,
|
||||||
|
TransitionDirection::Down);
|
||||||
|
case TransitionType::Scale:
|
||||||
|
return TransitionScaleScene::create(duration, inScene);
|
||||||
|
case TransitionType::Flip:
|
||||||
|
return TransitionFlipScene::create(duration, inScene);
|
||||||
|
case TransitionType::Box:
|
||||||
|
return TransitionBoxScene::create(duration, inScene);
|
||||||
|
default:
|
||||||
|
// 默认使用淡入淡出
|
||||||
|
return TransitionFadeScene::create(duration, inScene);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -625,9 +617,7 @@ void SceneManager::finishTransition() {
|
||||||
dispatchToNode(lastHoverTarget, evt);
|
dispatchToNode(lastHoverTarget, evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
outgoingScene_.reset();
|
activeTransitionScene_.reset();
|
||||||
incomingScene_.reset();
|
|
||||||
activeTransition_.reset();
|
|
||||||
transitionStackAction_ = nullptr;
|
transitionStackAction_ = nullptr;
|
||||||
|
|
||||||
if (transitionCallback_) {
|
if (transitionCallback_) {
|
||||||
|
|
|
||||||
|
|
@ -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 <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <extra2d/app/application.h>
|
#include <extra2d/app/application.h>
|
||||||
|
#include <extra2d/core/string.h>
|
||||||
#include <extra2d/graphics/render_backend.h>
|
#include <extra2d/graphics/render_backend.h>
|
||||||
#include <extra2d/ui/button.h>
|
#include <extra2d/ui/button.h>
|
||||||
#include <extra2d/core/string.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
|
@ -15,7 +15,7 @@ Button::Button() {
|
||||||
// 按钮默认锚点为左上角,这样setPosition(0, 0)会在左上角显示
|
// 按钮默认锚点为左上角,这样setPosition(0, 0)会在左上角显示
|
||||||
setAnchor(0.0f, 0.0f);
|
setAnchor(0.0f, 0.0f);
|
||||||
setSpatialIndexed(false);
|
setSpatialIndexed(false);
|
||||||
|
|
||||||
auto &dispatcher = getEventDispatcher();
|
auto &dispatcher = getEventDispatcher();
|
||||||
dispatcher.addListener(EventType::UIHoverEnter, [this](Event &) {
|
dispatcher.addListener(EventType::UIHoverEnter, [this](Event &) {
|
||||||
hovered_ = true;
|
hovered_ = true;
|
||||||
|
|
@ -37,24 +37,24 @@ Button::Button() {
|
||||||
dispatcher.addListener(EventType::UIReleased,
|
dispatcher.addListener(EventType::UIReleased,
|
||||||
[this](Event &) { pressed_ = false; });
|
[this](Event &) { pressed_ = false; });
|
||||||
dispatcher.addListener(EventType::UIClicked, [this](Event &) {
|
dispatcher.addListener(EventType::UIClicked, [this](Event &) {
|
||||||
if (onClick_)
|
if (toggleMode_) {
|
||||||
|
toggle();
|
||||||
|
}
|
||||||
|
if (onClick_) {
|
||||||
onClick_();
|
onClick_();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Button::Button(const std::string &text) : Button() {
|
Button::Button(const std::string &text) : Button() { text_ = text; }
|
||||||
text_ = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 静态创建方法
|
// 静态创建方法
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
Ptr<Button> Button::create() {
|
Ptr<Button> Button::create() { return makePtr<Button>(); }
|
||||||
return makePtr<Button>();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<Button> Button::create(const std::string &text) {
|
Ptr<Button> Button::create(const std::string &text) {
|
||||||
return makePtr<Button>(text);
|
return makePtr<Button>(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ptr<Button> Button::create(const std::string &text, Ptr<FontAtlas> font) {
|
Ptr<Button> Button::create(const std::string &text, Ptr<FontAtlas> font) {
|
||||||
|
|
@ -197,9 +197,7 @@ void Button::setPadding(const Vec2 &padding) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Button::setTextColor(const Color &color) {
|
void Button::setTextColor(const Color &color) { textColor_ = color; }
|
||||||
textColor_ = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Button::setBackgroundColor(const Color &normal, const Color &hover,
|
void Button::setBackgroundColor(const Color &normal, const Color &hover,
|
||||||
const Color &pressed) {
|
const Color &pressed) {
|
||||||
|
|
@ -229,10 +227,38 @@ void Button::setOnClick(Function<void()> callback) {
|
||||||
onClick_ = std::move(callback);
|
onClick_ = std::move(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Button::setHoverCursor(CursorShape cursor) {
|
void Button::setToggleMode(bool enabled) { toggleMode_ = enabled; }
|
||||||
hoverCursor_ = cursor;
|
|
||||||
|
void Button::setOn(bool on) {
|
||||||
|
if (isOn_ != on) {
|
||||||
|
isOn_ = on;
|
||||||
|
if (onStateChange_) {
|
||||||
|
onStateChange_(isOn_);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Button::toggle() { setOn(!isOn_); }
|
||||||
|
|
||||||
|
void Button::setOnStateChange(Function<void(bool)> callback) {
|
||||||
|
onStateChange_ = std::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Button::setStateText(const std::string &textOff,
|
||||||
|
const std::string &textOn) {
|
||||||
|
textOff_ = textOff;
|
||||||
|
textOn_ = textOn;
|
||||||
|
useStateText_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Button::setStateTextColor(const Color &colorOff, const Color &colorOn) {
|
||||||
|
textColorOff_ = colorOff;
|
||||||
|
textColorOn_ = colorOn;
|
||||||
|
useStateTextColor_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Button::setHoverCursor(CursorShape cursor) { hoverCursor_ = cursor; }
|
||||||
|
|
||||||
void Button::setBackgroundImage(Ptr<Texture> normal, Ptr<Texture> hover,
|
void Button::setBackgroundImage(Ptr<Texture> normal, Ptr<Texture> hover,
|
||||||
Ptr<Texture> pressed) {
|
Ptr<Texture> pressed) {
|
||||||
imgNormal_ = normal;
|
imgNormal_ = normal;
|
||||||
|
|
@ -256,12 +282,33 @@ void Button::setBackgroundImage(Ptr<Texture> texture, const Rect &rect) {
|
||||||
imgPressedRect_ = rect;
|
imgPressedRect_ = rect;
|
||||||
useImageBackground_ = (texture != nullptr);
|
useImageBackground_ = (texture != nullptr);
|
||||||
useTextureRect_ = true;
|
useTextureRect_ = true;
|
||||||
|
useStateImages_ = false;
|
||||||
|
|
||||||
if (useImageBackground_ && scaleMode_ == ImageScaleMode::Original) {
|
if (useImageBackground_ && scaleMode_ == ImageScaleMode::Original) {
|
||||||
setSize(rect.size.width, rect.size.height);
|
setSize(rect.size.width, rect.size.height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Button::setStateBackgroundImage(
|
||||||
|
Ptr<Texture> offNormal, Ptr<Texture> onNormal, Ptr<Texture> offHover,
|
||||||
|
Ptr<Texture> onHover, Ptr<Texture> offPressed, Ptr<Texture> onPressed) {
|
||||||
|
imgOffNormal_ = offNormal;
|
||||||
|
imgOnNormal_ = onNormal;
|
||||||
|
imgOffHover_ = offHover ? offHover : offNormal;
|
||||||
|
imgOnHover_ = onHover ? onHover : onNormal;
|
||||||
|
imgOffPressed_ = offPressed ? offPressed : offNormal;
|
||||||
|
imgOnPressed_ = onPressed ? onPressed : onNormal;
|
||||||
|
|
||||||
|
useStateImages_ = (offNormal != nullptr && onNormal != nullptr);
|
||||||
|
useImageBackground_ = useStateImages_;
|
||||||
|
useTextureRect_ = false;
|
||||||
|
|
||||||
|
if (useStateImages_ && scaleMode_ == ImageScaleMode::Original && offNormal) {
|
||||||
|
setSize(static_cast<float>(offNormal->getWidth()),
|
||||||
|
static_cast<float>(offNormal->getHeight()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Button::setBackgroundImageScaleMode(ImageScaleMode mode) {
|
void Button::setBackgroundImageScaleMode(ImageScaleMode mode) {
|
||||||
scaleMode_ = mode;
|
scaleMode_ = mode;
|
||||||
if (useImageBackground_ && scaleMode_ == ImageScaleMode::Original &&
|
if (useImageBackground_ && scaleMode_ == ImageScaleMode::Original &&
|
||||||
|
|
@ -271,9 +318,7 @@ void Button::setBackgroundImageScaleMode(ImageScaleMode mode) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Button::setCustomSize(const Vec2 &size) {
|
void Button::setCustomSize(const Vec2 &size) { setSize(size.x, size.y); }
|
||||||
setSize(size.x, size.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Button::setCustomSize(float width, float height) {
|
void Button::setCustomSize(float width, float height) {
|
||||||
setSize(width, height);
|
setSize(width, height);
|
||||||
|
|
@ -326,15 +371,50 @@ void Button::drawBackgroundImage(RenderBackend &renderer, const Rect &rect) {
|
||||||
Texture *texture = nullptr;
|
Texture *texture = nullptr;
|
||||||
Rect srcRect;
|
Rect srcRect;
|
||||||
|
|
||||||
if (pressed_ && imgPressed_) {
|
// 如果使用状态图片,根据 isOn_ 状态选择对应的图片
|
||||||
texture = imgPressed_.get();
|
if (useStateImages_) {
|
||||||
srcRect = useTextureRect_ ? imgPressedRect_ : Rect(0, 0, static_cast<float>(imgPressed_->getWidth()), static_cast<float>(imgPressed_->getHeight()));
|
if (isOn_) {
|
||||||
} else if (hovered_ && imgHover_) {
|
if (pressed_ && imgOnPressed_) {
|
||||||
texture = imgHover_.get();
|
texture = imgOnPressed_.get();
|
||||||
srcRect = useTextureRect_ ? imgHoverRect_ : Rect(0, 0, static_cast<float>(imgHover_->getWidth()), static_cast<float>(imgHover_->getHeight()));
|
} else if (hovered_ && imgOnHover_) {
|
||||||
} else if (imgNormal_) {
|
texture = imgOnHover_.get();
|
||||||
texture = imgNormal_.get();
|
} else {
|
||||||
srcRect = useTextureRect_ ? imgNormalRect_ : Rect(0, 0, static_cast<float>(imgNormal_->getWidth()), static_cast<float>(imgNormal_->getHeight()));
|
texture = imgOnNormal_.get();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (pressed_ && imgOffPressed_) {
|
||||||
|
texture = imgOffPressed_.get();
|
||||||
|
} else if (hovered_ && imgOffHover_) {
|
||||||
|
texture = imgOffHover_.get();
|
||||||
|
} else {
|
||||||
|
texture = imgOffNormal_.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (texture) {
|
||||||
|
srcRect = Rect(0, 0, static_cast<float>(texture->getWidth()),
|
||||||
|
static_cast<float>(texture->getHeight()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 使用普通图片背景
|
||||||
|
if (pressed_ && imgPressed_) {
|
||||||
|
texture = imgPressed_.get();
|
||||||
|
srcRect = useTextureRect_
|
||||||
|
? imgPressedRect_
|
||||||
|
: Rect(0, 0, static_cast<float>(imgPressed_->getWidth()),
|
||||||
|
static_cast<float>(imgPressed_->getHeight()));
|
||||||
|
} else if (hovered_ && imgHover_) {
|
||||||
|
texture = imgHover_.get();
|
||||||
|
srcRect = useTextureRect_
|
||||||
|
? imgHoverRect_
|
||||||
|
: Rect(0, 0, static_cast<float>(imgHover_->getWidth()),
|
||||||
|
static_cast<float>(imgHover_->getHeight()));
|
||||||
|
} else if (imgNormal_) {
|
||||||
|
texture = imgNormal_.get();
|
||||||
|
srcRect = useTextureRect_
|
||||||
|
? imgNormalRect_
|
||||||
|
: Rect(0, 0, static_cast<float>(imgNormal_->getWidth()),
|
||||||
|
static_cast<float>(imgNormal_->getHeight()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!texture)
|
if (!texture)
|
||||||
|
|
@ -349,8 +429,8 @@ void Button::drawBackgroundImage(RenderBackend &renderer, const Rect &rect) {
|
||||||
|
|
||||||
Rect destRect(drawPos.x, drawPos.y, drawSize.x, drawSize.y);
|
Rect destRect(drawPos.x, drawPos.y, drawSize.x, drawSize.y);
|
||||||
|
|
||||||
renderer.drawSprite(*texture, destRect, srcRect,
|
renderer.drawSprite(*texture, destRect, srcRect, Colors::White, 0.0f,
|
||||||
Colors::White, 0.0f, Vec2::Zero());
|
Vec2::Zero());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Button::drawRoundedRect(RenderBackend &renderer, const Rect &rect,
|
void Button::drawRoundedRect(RenderBackend &renderer, const Rect &rect,
|
||||||
|
|
@ -371,9 +451,11 @@ void Button::drawRoundedRect(RenderBackend &renderer, const Rect &rect,
|
||||||
float r = radius;
|
float r = radius;
|
||||||
|
|
||||||
renderer.drawLine(Vec2(x + r, y), Vec2(x + w - r, y), color, borderWidth_);
|
renderer.drawLine(Vec2(x + r, y), Vec2(x + w - r, y), color, borderWidth_);
|
||||||
renderer.drawLine(Vec2(x + r, y + h), Vec2(x + w - r, y + h), color, borderWidth_);
|
renderer.drawLine(Vec2(x + r, y + h), Vec2(x + w - r, y + h), color,
|
||||||
|
borderWidth_);
|
||||||
renderer.drawLine(Vec2(x, y + r), Vec2(x, y + h - r), color, borderWidth_);
|
renderer.drawLine(Vec2(x, y + r), Vec2(x, y + h - r), color, borderWidth_);
|
||||||
renderer.drawLine(Vec2(x + w, y + r), Vec2(x + w, y + h - r), color, borderWidth_);
|
renderer.drawLine(Vec2(x + w, y + r), Vec2(x + w, y + h - r), color,
|
||||||
|
borderWidth_);
|
||||||
|
|
||||||
for (int i = 0; i < segments; i++) {
|
for (int i = 0; i < segments; i++) {
|
||||||
float angle1 = 3.14159f * 0.5f * (float)i / segments + 3.14159f;
|
float angle1 = 3.14159f * 0.5f * (float)i / segments + 3.14159f;
|
||||||
|
|
@ -384,7 +466,8 @@ void Button::drawRoundedRect(RenderBackend &renderer, const Rect &rect,
|
||||||
}
|
}
|
||||||
for (int i = 0; i < segments; i++) {
|
for (int i = 0; i < segments; i++) {
|
||||||
float angle1 = 3.14159f * 0.5f * (float)i / segments + 3.14159f * 1.5f;
|
float angle1 = 3.14159f * 0.5f * (float)i / segments + 3.14159f * 1.5f;
|
||||||
float angle2 = 3.14159f * 0.5f * (float)(i + 1) / segments + 3.14159f * 1.5f;
|
float angle2 =
|
||||||
|
3.14159f * 0.5f * (float)(i + 1) / segments + 3.14159f * 1.5f;
|
||||||
Vec2 p1(x + w - r + r * cosf(angle1), y + r + r * sinf(angle1));
|
Vec2 p1(x + w - r + r * cosf(angle1), y + r + r * sinf(angle1));
|
||||||
Vec2 p2(x + w - r + r * cosf(angle2), y + r + r * sinf(angle2));
|
Vec2 p2(x + w - r + r * cosf(angle2), y + r + r * sinf(angle2));
|
||||||
renderer.drawLine(p1, p2, color, borderWidth_);
|
renderer.drawLine(p1, p2, color, borderWidth_);
|
||||||
|
|
@ -398,7 +481,8 @@ void Button::drawRoundedRect(RenderBackend &renderer, const Rect &rect,
|
||||||
}
|
}
|
||||||
for (int i = 0; i < segments; i++) {
|
for (int i = 0; i < segments; i++) {
|
||||||
float angle1 = 3.14159f * 0.5f * (float)i / segments + 3.14159f * 0.5f;
|
float angle1 = 3.14159f * 0.5f * (float)i / segments + 3.14159f * 0.5f;
|
||||||
float angle2 = 3.14159f * 0.5f * (float)(i + 1) / segments + 3.14159f * 0.5f;
|
float angle2 =
|
||||||
|
3.14159f * 0.5f * (float)(i + 1) / segments + 3.14159f * 0.5f;
|
||||||
Vec2 p1(x + r + r * cosf(angle1), y + h - r + r * sinf(angle1));
|
Vec2 p1(x + r + r * cosf(angle1), y + h - r + r * sinf(angle1));
|
||||||
Vec2 p2(x + r + r * cosf(angle2), y + h - r + r * sinf(angle2));
|
Vec2 p2(x + r + r * cosf(angle2), y + h - r + r * sinf(angle2));
|
||||||
renderer.drawLine(p1, p2, color, borderWidth_);
|
renderer.drawLine(p1, p2, color, borderWidth_);
|
||||||
|
|
@ -513,169 +597,40 @@ void Button::onDrawWidget(RenderBackend &renderer) {
|
||||||
renderer.beginSpriteBatch();
|
renderer.beginSpriteBatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (font_ && !text_.empty()) {
|
if (font_) {
|
||||||
Vec2 textSize = font_->measureText(text_);
|
// 确定要显示的文字
|
||||||
|
|
||||||
Vec2 textPos(rect.center().x - textSize.x * 0.5f,
|
|
||||||
rect.center().y - textSize.y * 0.5f);
|
|
||||||
|
|
||||||
float minX = rect.left() + padding_.x;
|
|
||||||
float minY = rect.top() + padding_.y;
|
|
||||||
float maxX = rect.right() - padding_.x - textSize.x;
|
|
||||||
float maxY = rect.bottom() - padding_.y - textSize.y;
|
|
||||||
|
|
||||||
textPos.x = std::max(minX, std::min(textPos.x, maxX));
|
|
||||||
textPos.y = std::max(minY, std::min(textPos.y, maxY));
|
|
||||||
|
|
||||||
Color finalTextColor = textColor_;
|
|
||||||
finalTextColor.a = 1.0f;
|
|
||||||
|
|
||||||
renderer.drawText(*font_, text_, textPos, finalTextColor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// ToggleImageButton 实现
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
ToggleImageButton::ToggleImageButton() {
|
|
||||||
setOnClick([this]() { toggle(); });
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<ToggleImageButton> ToggleImageButton::create() {
|
|
||||||
return makePtr<ToggleImageButton>();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ToggleImageButton::setStateImages(Ptr<Texture> stateOffNormal,
|
|
||||||
Ptr<Texture> stateOnNormal,
|
|
||||||
Ptr<Texture> stateOffHover,
|
|
||||||
Ptr<Texture> stateOnHover,
|
|
||||||
Ptr<Texture> stateOffPressed,
|
|
||||||
Ptr<Texture> stateOnPressed) {
|
|
||||||
imgOffNormal_ = stateOffNormal;
|
|
||||||
imgOnNormal_ = stateOnNormal;
|
|
||||||
imgOffHover_ = stateOffHover ? stateOffHover : stateOffNormal;
|
|
||||||
imgOnHover_ = stateOnHover ? stateOnHover : stateOnNormal;
|
|
||||||
imgOffPressed_ = stateOffPressed ? stateOffPressed : stateOffNormal;
|
|
||||||
imgOnPressed_ = stateOnPressed ? stateOnPressed : stateOnNormal;
|
|
||||||
|
|
||||||
if (imgOffNormal_) {
|
|
||||||
setSize(static_cast<float>(imgOffNormal_->getWidth()),
|
|
||||||
static_cast<float>(imgOffNormal_->getHeight()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ToggleImageButton::setOn(bool on) {
|
|
||||||
if (isOn_ != on) {
|
|
||||||
isOn_ = on;
|
|
||||||
if (onStateChange_) {
|
|
||||||
onStateChange_(isOn_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ToggleImageButton::toggle() {
|
|
||||||
setOn(!isOn_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ToggleImageButton::setOnStateChange(Function<void(bool)> callback) {
|
|
||||||
onStateChange_ = std::move(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ToggleImageButton::setStateText(const std::string &textOff,
|
|
||||||
const std::string &textOn) {
|
|
||||||
textOff_ = textOff;
|
|
||||||
textOn_ = textOn;
|
|
||||||
useStateText_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ToggleImageButton::setStateTextColor(const Color &colorOff,
|
|
||||||
const Color &colorOn) {
|
|
||||||
textColorOff_ = colorOff;
|
|
||||||
textColorOn_ = colorOn;
|
|
||||||
useStateTextColor_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ToggleImageButton::onDrawWidget(RenderBackend &renderer) {
|
|
||||||
Rect rect = getBoundingBox();
|
|
||||||
if (rect.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<Texture> texture = nullptr;
|
|
||||||
|
|
||||||
if (isOn_) {
|
|
||||||
if (isPressed() && imgOnPressed_) {
|
|
||||||
texture = imgOnPressed_;
|
|
||||||
} else if (isHovered() && imgOnHover_) {
|
|
||||||
texture = imgOnHover_;
|
|
||||||
} else {
|
|
||||||
texture = imgOnNormal_;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isPressed() && imgOffPressed_) {
|
|
||||||
texture = imgOffPressed_;
|
|
||||||
} else if (isHovered() && imgOffHover_) {
|
|
||||||
texture = imgOffHover_;
|
|
||||||
} else {
|
|
||||||
texture = imgOffNormal_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (texture) {
|
|
||||||
Vec2 imageSize(static_cast<float>(texture->getWidth()),
|
|
||||||
static_cast<float>(texture->getHeight()));
|
|
||||||
Vec2 buttonSize(rect.size.width, rect.size.height);
|
|
||||||
Vec2 drawSize = imageSize;
|
|
||||||
|
|
||||||
Vec2 drawPos(rect.origin.x + (buttonSize.x - drawSize.x) * 0.5f,
|
|
||||||
rect.origin.y + (buttonSize.y - drawSize.y) * 0.5f);
|
|
||||||
|
|
||||||
Rect destRect(drawPos.x, drawPos.y, drawSize.x, drawSize.y);
|
|
||||||
renderer.drawSprite(*texture, destRect,
|
|
||||||
Rect(0, 0, imageSize.x, imageSize.y), Colors::White,
|
|
||||||
0.0f, Vec2::Zero());
|
|
||||||
}
|
|
||||||
|
|
||||||
renderer.endSpriteBatch();
|
|
||||||
|
|
||||||
float borderWidth = 1.0f;
|
|
||||||
Color borderColor =
|
|
||||||
isOn_ ? Color(0.0f, 1.0f, 0.0f, 0.8f) : Color(0.6f, 0.6f, 0.6f, 1.0f);
|
|
||||||
if (borderWidth > 0.0f) {
|
|
||||||
if (isRoundedCornersEnabled()) {
|
|
||||||
drawRoundedRect(renderer, rect, borderColor, getCornerRadius());
|
|
||||||
} else {
|
|
||||||
renderer.drawRect(rect, borderColor, borderWidth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderer.beginSpriteBatch();
|
|
||||||
|
|
||||||
auto font = getFont();
|
|
||||||
if (font) {
|
|
||||||
std::string textToDraw;
|
std::string textToDraw;
|
||||||
if (useStateText_) {
|
if (useStateText_) {
|
||||||
textToDraw = isOn_ ? textOn_ : textOff_;
|
textToDraw = isOn_ ? textOn_ : textOff_;
|
||||||
} else {
|
} else {
|
||||||
textToDraw = getText();
|
textToDraw = text_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 确定文字颜色
|
||||||
Color colorToUse;
|
Color colorToUse;
|
||||||
if (useStateTextColor_) {
|
if (useStateTextColor_) {
|
||||||
colorToUse = isOn_ ? textColorOn_ : textColorOff_;
|
colorToUse = isOn_ ? textColorOn_ : textColorOff_;
|
||||||
} else {
|
} else {
|
||||||
colorToUse = getTextColor();
|
colorToUse = textColor_;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!textToDraw.empty()) {
|
if (!textToDraw.empty()) {
|
||||||
Vec2 textSize = font->measureText(textToDraw);
|
Vec2 textSize = font_->measureText(textToDraw);
|
||||||
|
|
||||||
Vec2 textPos(rect.center().x - textSize.x * 0.5f,
|
Vec2 textPos(rect.center().x - textSize.x * 0.5f,
|
||||||
rect.center().y - textSize.y * 0.5f);
|
rect.center().y - textSize.y * 0.5f);
|
||||||
|
|
||||||
|
float minX = rect.left() + padding_.x;
|
||||||
|
float minY = rect.top() + padding_.y;
|
||||||
|
float maxX = rect.right() - padding_.x - textSize.x;
|
||||||
|
float maxY = rect.bottom() - padding_.y - textSize.y;
|
||||||
|
|
||||||
|
textPos.x = std::max(minX, std::min(textPos.x, maxX));
|
||||||
|
textPos.y = std::max(minY, std::min(textPos.y, maxY));
|
||||||
|
|
||||||
colorToUse.a = 1.0f;
|
colorToUse.a = 1.0f;
|
||||||
|
|
||||||
renderer.drawText(*font, textToDraw, textPos, colorToUse);
|
renderer.drawText(*font_, textToDraw, textPos, colorToUse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ Extra2D 的场景系统提供了游戏内容的分层管理和切换功能。本
|
||||||
|
|
||||||
## 完整示例
|
## 完整示例
|
||||||
|
|
||||||
参考 `examples/flappy_bird/` 中的实现:
|
参考 `examples/flappy_bird/` 和 `examples/push_box/` 中的实现:
|
||||||
|
|
||||||
- `BaseScene.h/cpp` - 基础场景类(视口适配)
|
- `BaseScene.h/cpp` - 基础场景类(视口适配)
|
||||||
- `StartScene.h/cpp` - 开始菜单场景
|
- `StartScene.h/cpp` - 开始菜单场景
|
||||||
|
|
@ -193,6 +193,14 @@ void GameScene::onUpdate(float dt) {
|
||||||
4. **所有坐标使用逻辑分辨率** - 不依赖窗口实际大小
|
4. **所有坐标使用逻辑分辨率** - 不依赖窗口实际大小
|
||||||
5. **处理窗口大小变化** - 在 `onRender()` 中检测并更新
|
5. **处理窗口大小变化** - 在 `onRender()` 中检测并更新
|
||||||
|
|
||||||
|
### 注意事项
|
||||||
|
|
||||||
|
1. **相机设置必须正确** - 使用 `camera->setViewport(0.0f, GAME_WIDTH, GAME_HEIGHT, 0.0f)` 确保相机覆盖整个游戏区域
|
||||||
|
2. **渲染器视口与相机视口不同** - 渲染器视口控制实际渲染到屏幕的区域,相机视口控制世界坐标到屏幕坐标的映射
|
||||||
|
3. **窗口大小变化检测** - 在 `onRender()` 中检测窗口大小变化并重新计算视口,确保窗口调整时正确适配
|
||||||
|
4. **子类必须调用父类方法** - `onEnter()` 和 `onUpdate()` 中必须调用 `BaseScene::onEnter()` 和 `BaseScene::onUpdate()`
|
||||||
|
5. **UI元素坐标空间** - UI控件通常使用 `CoordinateSpace::Screen` 固定在屏幕上,不受视口适配影响
|
||||||
|
|
||||||
## 场景基础
|
## 场景基础
|
||||||
|
|
||||||
### 创建场景
|
### 创建场景
|
||||||
|
|
@ -230,23 +238,37 @@ public:
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### 场景切换
|
### 场景切换(新API)
|
||||||
|
|
||||||
|
通过 `SceneManager` 进行场景切换:
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
// 进入场景(无过渡)
|
auto& scenes = app.scenes();
|
||||||
app.enterScene(makePtr<GameScene>());
|
|
||||||
|
|
||||||
// 进入场景(有过渡效果)
|
// 运行第一个场景
|
||||||
app.enterScene(makePtr<GameScene>(), TransitionType::Fade, 0.5f);
|
scenes.runWithScene(makePtr<GameScene>());
|
||||||
|
|
||||||
// 替换当前场景
|
// 替换当前场景(无过渡)
|
||||||
app.scenes().replaceScene(makePtr<NewScene>());
|
scenes.replaceScene(makePtr<GameScene>());
|
||||||
|
|
||||||
|
// 替换当前场景(有过渡效果)
|
||||||
|
scenes.replaceScene(makePtr<GameScene>(), TransitionType::Fade, 0.5f);
|
||||||
|
|
||||||
// 推入场景(保留当前场景)
|
// 推入场景(保留当前场景)
|
||||||
app.scenes().pushScene(makePtr<NewScene>());
|
scenes.pushScene(makePtr<NewScene>());
|
||||||
|
scenes.pushScene(makePtr<NewScene>(), TransitionType::SlideLeft, 0.5f);
|
||||||
|
|
||||||
// 弹出场景(返回上一个场景)
|
// 弹出场景(返回上一个场景)
|
||||||
app.scenes().popScene();
|
scenes.popScene();
|
||||||
|
scenes.popScene(TransitionType::Fade, 0.5f);
|
||||||
|
|
||||||
|
// 弹出到根场景
|
||||||
|
scenes.popToRootScene();
|
||||||
|
scenes.popToRootScene(TransitionType::Fade, 0.5f);
|
||||||
|
|
||||||
|
// 弹出到指定场景
|
||||||
|
scenes.popToScene("SceneName");
|
||||||
|
scenes.popToScene("SceneName", TransitionType::Fade, 0.5f);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 过渡效果类型
|
### 过渡效果类型
|
||||||
|
|
@ -258,11 +280,14 @@ enum class TransitionType {
|
||||||
SlideLeft, // 向左滑动
|
SlideLeft, // 向左滑动
|
||||||
SlideRight, // 向右滑动
|
SlideRight, // 向右滑动
|
||||||
SlideUp, // 向上滑动
|
SlideUp, // 向上滑动
|
||||||
SlideDown // 向下滑动
|
SlideDown, // 向下滑动
|
||||||
|
Scale, // 缩放过渡
|
||||||
|
Flip, // 翻转过渡
|
||||||
|
Box // 盒子过渡
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## 场景管理器
|
### 场景管理器
|
||||||
|
|
||||||
通过 `app.scenes()` 访问场景管理器:
|
通过 `app.scenes()` 访问场景管理器:
|
||||||
|
|
||||||
|
|
@ -270,13 +295,25 @@ enum class TransitionType {
|
||||||
auto& scenes = app.scenes();
|
auto& scenes = app.scenes();
|
||||||
|
|
||||||
// 获取当前场景
|
// 获取当前场景
|
||||||
auto current = scenes.currentScene();
|
auto current = scenes.getCurrentScene();
|
||||||
|
|
||||||
|
// 获取上一个场景
|
||||||
|
auto previous = scenes.getPreviousScene();
|
||||||
|
|
||||||
|
// 获取根场景
|
||||||
|
auto root = scenes.getRootScene();
|
||||||
|
|
||||||
|
// 通过名称获取场景
|
||||||
|
auto scene = scenes.getSceneByName("SceneName");
|
||||||
|
|
||||||
// 获取场景栈深度
|
// 获取场景栈深度
|
||||||
size_t depth = scenes.stackDepth();
|
size_t count = scenes.getSceneCount();
|
||||||
|
|
||||||
|
// 检查是否正在过渡
|
||||||
|
bool transitioning = scenes.isTransitioning();
|
||||||
|
|
||||||
// 清空场景栈
|
// 清空场景栈
|
||||||
scenes.clearStack();
|
scenes.end();
|
||||||
```
|
```
|
||||||
|
|
||||||
## 场景生命周期
|
## 场景生命周期
|
||||||
|
|
@ -284,7 +321,7 @@ scenes.clearStack();
|
||||||
```
|
```
|
||||||
创建场景 (makePtr<Scene>)
|
创建场景 (makePtr<Scene>)
|
||||||
↓
|
↓
|
||||||
进入场景 (enterScene)
|
进入场景 (runWithScene/replaceScene/pushScene)
|
||||||
↓
|
↓
|
||||||
onEnter() - 初始化资源
|
onEnter() - 初始化资源
|
||||||
↓
|
↓
|
||||||
|
|
@ -411,10 +448,12 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
void executeMenuItem() {
|
void executeMenuItem() {
|
||||||
|
auto& scenes = Application::instance().scenes();
|
||||||
|
|
||||||
switch (selectedIndex_) {
|
switch (selectedIndex_) {
|
||||||
case 0:
|
case 0:
|
||||||
Application::instance().scenes().replaceScene(
|
scenes.replaceScene(makePtr<GameScene>(),
|
||||||
makePtr<GameScene>(), TransitionType::Fade, 0.25f);
|
TransitionType::Fade, 0.25f);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
// 打开设置
|
// 打开设置
|
||||||
|
|
@ -438,6 +477,8 @@ private:
|
||||||
2. **在 onExit 中清理资源** - 避免内存泄漏
|
2. **在 onExit 中清理资源** - 避免内存泄漏
|
||||||
3. **使用过渡效果** - 提升用户体验
|
3. **使用过渡效果** - 提升用户体验
|
||||||
4. **分离场景逻辑** - 每个场景负责自己的功能
|
4. **分离场景逻辑** - 每个场景负责自己的功能
|
||||||
|
5. **使用视口适配** - 确保游戏在不同分辨率下正确显示
|
||||||
|
6. **正确处理窗口大小变化** - 在 `onRender()` 中检测并更新视口
|
||||||
|
|
||||||
## 下一步
|
## 下一步
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ button->setBorder(Colors::White, 2.0f);
|
||||||
button->setRoundedCornersEnabled(true);
|
button->setRoundedCornersEnabled(true);
|
||||||
button->setCornerRadius(8.0f);
|
button->setCornerRadius(8.0f);
|
||||||
|
|
||||||
// 图片背景
|
// 图片背景(普通按钮)
|
||||||
button->setBackgroundImage(normalTex, hoverTex, pressedTex);
|
button->setBackgroundImage(normalTex, hoverTex, pressedTex);
|
||||||
button->setBackgroundImageScaleMode(ImageScaleMode::ScaleFit);
|
button->setBackgroundImageScaleMode(ImageScaleMode::ScaleFit);
|
||||||
|
|
||||||
|
|
@ -117,6 +117,51 @@ button->setBackgroundImageScaleMode(ImageScaleMode::ScaleFit);
|
||||||
button->setHoverCursor(CursorShape::Hand);
|
button->setHoverCursor(CursorShape::Hand);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 切换按钮模式(Toggle Button)
|
||||||
|
|
||||||
|
按钮支持切换模式,可以在 on/off 两种状态间切换:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// 创建切换按钮
|
||||||
|
auto toggleBtn = Button::create();
|
||||||
|
toggleBtn->setToggleMode(true); // 启用切换模式
|
||||||
|
|
||||||
|
// 设置状态图片(off状态图片, on状态图片)
|
||||||
|
toggleBtn->setStateBackgroundImage(soundOffTex, soundOnTex);
|
||||||
|
|
||||||
|
// 或设置带悬停/按下效果的状态图片
|
||||||
|
toggleBtn->setStateBackgroundImage(
|
||||||
|
soundOffTex, soundOnTex, // 普通状态
|
||||||
|
soundOffHoverTex, soundOnHoverTex, // 悬停状态(可选)
|
||||||
|
soundOffPressedTex, soundOnPressedTex // 按下状态(可选)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 设置状态文字
|
||||||
|
toggleBtn->setStateText("关闭", "开启");
|
||||||
|
|
||||||
|
// 设置状态文字颜色
|
||||||
|
toggleBtn->setStateTextColor(Colors::Red, Colors::Green);
|
||||||
|
|
||||||
|
// 设置初始状态
|
||||||
|
toggleBtn->setOn(true);
|
||||||
|
|
||||||
|
// 获取当前状态
|
||||||
|
bool isOn = toggleBtn->isOn();
|
||||||
|
|
||||||
|
// 手动切换状态
|
||||||
|
toggleBtn->toggle();
|
||||||
|
|
||||||
|
// 状态改变回调
|
||||||
|
toggleBtn->setOnStateChange([](bool isOn) {
|
||||||
|
E2D_LOG_INFO("切换按钮状态: {}", isOn ? "开启" : "关闭");
|
||||||
|
});
|
||||||
|
|
||||||
|
// 点击回调(切换模式也会触发此回调)
|
||||||
|
toggleBtn->setOnClick([]() {
|
||||||
|
E2D_LOG_INFO("按钮被点击");
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
### 图片缩放模式
|
### 图片缩放模式
|
||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
|
|
@ -245,6 +290,7 @@ void GameOverLayer::onUpdate(float dt) {
|
||||||
- **动画播放期间**:禁用按钮,防止用户过早操作
|
- **动画播放期间**:禁用按钮,防止用户过早操作
|
||||||
- **加载过程中**:禁用按钮,显示加载状态
|
- **加载过程中**:禁用按钮,显示加载状态
|
||||||
- **条件限制**:当条件不满足时禁用按钮(如未选择关卡)
|
- **条件限制**:当条件不满足时禁用按钮(如未选择关卡)
|
||||||
|
- **切换按钮**:音效开关、设置开关等需要显示两种状态的按钮
|
||||||
|
|
||||||
## 文本(Text)
|
## 文本(Text)
|
||||||
|
|
||||||
|
|
@ -350,11 +396,11 @@ checkBox->setPosition(Vec2(100, 200));
|
||||||
checkBox->setChecked(true);
|
checkBox->setChecked(true);
|
||||||
|
|
||||||
// 方式2:带标签
|
// 方式2:带标签
|
||||||
auto checkBox = CheckBox::create("启用音效");
|
checkBox = CheckBox::create("启用音效");
|
||||||
checkBox->setPosition(Vec2(100, 200));
|
checkBox->setPosition(Vec2(100, 200));
|
||||||
|
|
||||||
// 方式3:链式调用
|
// 方式3:链式调用
|
||||||
auto checkBox = CheckBox::create("启用音效")
|
checkBox = CheckBox::create("启用音效")
|
||||||
->withPosition(100, 200)
|
->withPosition(100, 200)
|
||||||
->withFont(font)
|
->withFont(font)
|
||||||
->withTextColor(Colors::White);
|
->withTextColor(Colors::White);
|
||||||
|
|
@ -620,18 +666,24 @@ public:
|
||||||
title->setAlignment(Alignment::Center);
|
title->setAlignment(Alignment::Center);
|
||||||
addChild(title);
|
addChild(title);
|
||||||
|
|
||||||
// 音效开关
|
// 音效开关(使用切换按钮)
|
||||||
auto soundLabel = Label::create("音效", font_);
|
auto soundLabel = Label::create("音效", font_);
|
||||||
soundLabel->setPosition(Vec2(200, 200));
|
soundLabel->setPosition(Vec2(200, 200));
|
||||||
addChild(soundLabel);
|
addChild(soundLabel);
|
||||||
|
|
||||||
soundCheck_ = CheckBox::create();
|
auto soundOn = resources.loadTexture("assets/sound_on.png");
|
||||||
soundCheck_->setPosition(Vec2(350, 200));
|
auto soundOff = resources.loadTexture("assets/sound_off.png");
|
||||||
soundCheck_->setChecked(true);
|
|
||||||
soundCheck_->setOnStateChange([this](bool checked) {
|
soundToggle_ = Button::create();
|
||||||
E2D_LOG_INFO("音效: {}", checked ? "开启" : "关闭");
|
soundToggle_->setPosition(Vec2(350, 200));
|
||||||
|
soundToggle_->setToggleMode(true);
|
||||||
|
soundToggle_->setStateBackgroundImage(soundOff, soundOn);
|
||||||
|
soundToggle_->setOn(true);
|
||||||
|
soundToggle_->setOnStateChange([](bool isOn) {
|
||||||
|
E2D_LOG_INFO("音效: {}", isOn ? "开启" : "关闭");
|
||||||
|
AudioManager::instance().setEnabled(isOn);
|
||||||
});
|
});
|
||||||
addChild(soundCheck_);
|
addChild(soundToggle_);
|
||||||
|
|
||||||
// 音量滑块
|
// 音量滑块
|
||||||
auto volumeLabel = Label::create("音量", font_);
|
auto volumeLabel = Label::create("音量", font_);
|
||||||
|
|
@ -685,7 +737,7 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ptr<FontAtlas> font_;
|
Ptr<FontAtlas> font_;
|
||||||
Ptr<CheckBox> soundCheck_;
|
Ptr<Button> soundToggle_;
|
||||||
Ptr<Slider> volumeSlider_;
|
Ptr<Slider> volumeSlider_;
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
@ -697,6 +749,7 @@ private:
|
||||||
3. **设置合适的锚点** - 使用锚点(0.5, 0.5)让控件中心对齐,方便布局
|
3. **设置合适的锚点** - 使用锚点(0.5, 0.5)让控件中心对齐,方便布局
|
||||||
4. **复用字体资源** - 避免重复加载相同字体
|
4. **复用字体资源** - 避免重复加载相同字体
|
||||||
5. **使用回调函数** - 使用 `setOnClick`、`setOnValueChange` 等回调响应用户操作
|
5. **使用回调函数** - 使用 `setOnClick`、`setOnValueChange` 等回调响应用户操作
|
||||||
|
6. **使用切换按钮** - 对于需要显示两种状态的按钮(如开关),使用 `setToggleMode(true)`
|
||||||
|
|
||||||
## 下一步
|
## 下一步
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
#include "BaseScene.h"
|
#include "BaseScene.h"
|
||||||
|
#include <extra2d/scene/transition_scene.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
namespace flappybird {
|
namespace flappybird {
|
||||||
|
|
@ -66,10 +67,6 @@ void BaseScene::onRender(extra2d::RenderBackend &renderer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置视口为居中区域
|
// 设置视口为居中区域
|
||||||
E2D_LOG_INFO(
|
|
||||||
"BaseScene::onRender - setting viewport: x={}, y={}, width={}, height={}",
|
|
||||||
static_cast<int>(viewportOffsetX_), static_cast<int>(viewportOffsetY_),
|
|
||||||
static_cast<int>(scaledGameWidth_), static_cast<int>(scaledGameHeight_));
|
|
||||||
renderer.setViewport(
|
renderer.setViewport(
|
||||||
static_cast<int>(viewportOffsetX_), static_cast<int>(viewportOffsetY_),
|
static_cast<int>(viewportOffsetX_), static_cast<int>(viewportOffsetY_),
|
||||||
static_cast<int>(scaledGameWidth_), static_cast<int>(scaledGameHeight_));
|
static_cast<int>(scaledGameWidth_), static_cast<int>(scaledGameHeight_));
|
||||||
|
|
@ -78,4 +75,53 @@ void BaseScene::onRender(extra2d::RenderBackend &renderer) {
|
||||||
extra2d::Scene::onRender(renderer);
|
extra2d::Scene::onRender(renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BaseScene::renderContent(extra2d::RenderBackend &renderer) {
|
||||||
|
// 如果视口参数未初始化(onEnter 还没被调用),先初始化
|
||||||
|
if (scaledGameWidth_ <= 0.0f || scaledGameHeight_ <= 0.0f) {
|
||||||
|
updateViewport();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查窗口大小是否改变
|
||||||
|
auto &app = extra2d::Application::instance();
|
||||||
|
float currentWindowWidth = static_cast<float>(app.window().getWidth());
|
||||||
|
float currentWindowHeight = static_cast<float>(app.window().getHeight());
|
||||||
|
|
||||||
|
float expectedWidth = scaledGameWidth_ + viewportOffsetX_ * 2.0f;
|
||||||
|
float expectedHeight = scaledGameHeight_ + viewportOffsetY_ * 2.0f;
|
||||||
|
if (std::abs(currentWindowWidth - expectedWidth) > 1.0f ||
|
||||||
|
std::abs(currentWindowHeight - expectedHeight) > 1.0f) {
|
||||||
|
updateViewport();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查当前场景是否作为 TransitionScene 的子场景被渲染
|
||||||
|
bool isChildOfTransition = false;
|
||||||
|
if (auto parent = getParent()) {
|
||||||
|
if (dynamic_cast<extra2d::TransitionScene *>(parent.get())) {
|
||||||
|
isChildOfTransition = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isChildOfTransition) {
|
||||||
|
// 作为 TransitionScene 的子场景时,需要设置正确的投影矩阵
|
||||||
|
// 使用游戏逻辑分辨率作为投影区域,让 TransitionScene 控制整体视口
|
||||||
|
auto camera = getActiveCamera();
|
||||||
|
if (camera) {
|
||||||
|
// 设置投影矩阵覆盖整个游戏逻辑区域
|
||||||
|
renderer.setViewProjection(camera->getViewProjectionMatrix());
|
||||||
|
}
|
||||||
|
// 渲染场景内容(投影矩阵已设置,直接渲染)
|
||||||
|
batchUpdateTransforms();
|
||||||
|
renderer.beginSpriteBatch();
|
||||||
|
render(renderer);
|
||||||
|
renderer.endSpriteBatch();
|
||||||
|
} else {
|
||||||
|
// 正常渲染时,调用父类的 renderContent 处理视口和投影
|
||||||
|
renderer.setViewport(static_cast<int>(viewportOffsetX_),
|
||||||
|
static_cast<int>(viewportOffsetY_),
|
||||||
|
static_cast<int>(scaledGameWidth_),
|
||||||
|
static_cast<int>(scaledGameHeight_));
|
||||||
|
extra2d::Scene::renderContent(renderer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace flappybird
|
} // namespace flappybird
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,12 @@ public:
|
||||||
*/
|
*/
|
||||||
void onRender(extra2d::RenderBackend &renderer) override;
|
void onRender(extra2d::RenderBackend &renderer) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染场景内容,确保视口正确设置
|
||||||
|
* @param renderer 渲染后端
|
||||||
|
*/
|
||||||
|
void renderContent(extra2d::RenderBackend &renderer) override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/**
|
/**
|
||||||
* @brief 更新视口计算,使游戏内容在窗口中居中显示
|
* @brief 更新视口计算,使游戏内容在窗口中居中显示
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ void SplashScene::onUpdate(float dt) {
|
||||||
void SplashScene::gotoStartScene() {
|
void SplashScene::gotoStartScene() {
|
||||||
auto &app = extra2d::Application::instance();
|
auto &app = extra2d::Application::instance();
|
||||||
app.scenes().replaceScene(extra2d::makePtr<StartScene>(),
|
app.scenes().replaceScene(extra2d::makePtr<StartScene>(),
|
||||||
extra2d::TransitionType::Fade, 0.5f);
|
extra2d::TransitionType::Fade, 2.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace flappybird
|
} // namespace flappybird
|
||||||
|
|
|
||||||
|
|
@ -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,386 +1,418 @@
|
||||||
|
// ============================================================================
|
||||||
|
// PlayScene.cpp - Push Box 游戏场景实现
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
#include "PlayScene.h"
|
#include "PlayScene.h"
|
||||||
|
|
||||||
#include "audio_manager.h"
|
|
||||||
#include "storage.h"
|
|
||||||
#include "StartScene.h"
|
#include "StartScene.h"
|
||||||
#include "SuccessScene.h"
|
#include "SuccessScene.h"
|
||||||
|
#include "audio_manager.h"
|
||||||
|
#include "storage.h"
|
||||||
#include <extra2d/extra2d.h>
|
#include <extra2d/extra2d.h>
|
||||||
|
|
||||||
namespace pushbox {
|
namespace pushbox {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 加载字体
|
||||||
|
* @param size 字体大小
|
||||||
|
*/
|
||||||
static extra2d::Ptr<extra2d::FontAtlas> loadFont(int size) {
|
static extra2d::Ptr<extra2d::FontAtlas> loadFont(int size) {
|
||||||
auto& resources = extra2d::Application::instance().resources();
|
auto &resources = extra2d::Application::instance().resources();
|
||||||
auto font = resources.loadFont("assets/font.ttf", size);
|
auto font = resources.loadFont("assets/font.ttf", size);
|
||||||
return font;
|
return font;
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayScene::PlayScene(int level) {
|
PlayScene::PlayScene(int level) : BaseScene() {
|
||||||
setBackgroundColor(extra2d::Colors::Black);
|
auto &app = extra2d::Application::instance();
|
||||||
|
auto &resources = app.resources();
|
||||||
|
|
||||||
auto& app = extra2d::Application::instance();
|
E2D_LOG_INFO("PlayScene: Loading textures...");
|
||||||
auto& config = app.getConfig();
|
|
||||||
setViewportSize(static_cast<float>(config.width), static_cast<float>(config.height));
|
|
||||||
|
|
||||||
auto& resources = app.resources();
|
texWall_ = resources.loadTexture("assets/images/wall.gif");
|
||||||
|
texPoint_ = resources.loadTexture("assets/images/point.gif");
|
||||||
|
texFloor_ = resources.loadTexture("assets/images/floor.gif");
|
||||||
|
texBox_ = resources.loadTexture("assets/images/box.gif");
|
||||||
|
texBoxInPoint_ = resources.loadTexture("assets/images/boxinpoint.gif");
|
||||||
|
|
||||||
E2D_LOG_INFO("PlayScene: Loading textures...");
|
texMan_[1] = resources.loadTexture("assets/images/player/manup.gif");
|
||||||
|
texMan_[2] = resources.loadTexture("assets/images/player/mandown.gif");
|
||||||
|
texMan_[3] = resources.loadTexture("assets/images/player/manleft.gif");
|
||||||
|
texMan_[4] = resources.loadTexture("assets/images/player/manright.gif");
|
||||||
|
|
||||||
texWall_ = resources.loadTexture("assets/images/wall.gif");
|
texManPush_[1] = resources.loadTexture("assets/images/player/manhandup.gif");
|
||||||
texPoint_ = resources.loadTexture("assets/images/point.gif");
|
texManPush_[2] =
|
||||||
texFloor_ = resources.loadTexture("assets/images/floor.gif");
|
resources.loadTexture("assets/images/player/manhanddown.gif");
|
||||||
texBox_ = resources.loadTexture("assets/images/box.gif");
|
texManPush_[3] =
|
||||||
texBoxInPoint_ = resources.loadTexture("assets/images/boxinpoint.gif");
|
resources.loadTexture("assets/images/player/manhandleft.gif");
|
||||||
|
texManPush_[4] =
|
||||||
|
resources.loadTexture("assets/images/player/manhandright.gif");
|
||||||
|
|
||||||
texMan_[1] = resources.loadTexture("assets/images/player/manup.gif");
|
font28_ = loadFont(28);
|
||||||
texMan_[2] = resources.loadTexture("assets/images/player/mandown.gif");
|
font20_ = loadFont(20);
|
||||||
texMan_[3] = resources.loadTexture("assets/images/player/manleft.gif");
|
|
||||||
texMan_[4] = resources.loadTexture("assets/images/player/manright.gif");
|
|
||||||
|
|
||||||
texManPush_[1] = resources.loadTexture("assets/images/player/manhandup.gif");
|
// 使用游戏逻辑分辨率
|
||||||
texManPush_[2] = resources.loadTexture("assets/images/player/manhanddown.gif");
|
float screenW = GAME_WIDTH;
|
||||||
texManPush_[3] = resources.loadTexture("assets/images/player/manhandleft.gif");
|
float screenH = GAME_HEIGHT;
|
||||||
texManPush_[4] = resources.loadTexture("assets/images/player/manhandright.gif");
|
|
||||||
|
|
||||||
font28_ = loadFont(28);
|
// 计算游戏区域居中偏移
|
||||||
font20_ = loadFont(20);
|
float offsetX = (screenW - GAME_WIDTH) / 2.0f;
|
||||||
|
float offsetY = (screenH - GAME_HEIGHT) / 2.0f;
|
||||||
|
|
||||||
// 获取窗口尺寸
|
// 音效开关按钮(使用 Button 的切换模式)
|
||||||
float screenW = static_cast<float>(app.getConfig().width);
|
auto soundOn = resources.loadTexture("assets/images/soundon.png");
|
||||||
float screenH = static_cast<float>(app.getConfig().height);
|
auto soundOff = resources.loadTexture("assets/images/soundoff.png");
|
||||||
|
if (soundOn && soundOff) {
|
||||||
|
soundBtn_ = extra2d::Button::create();
|
||||||
|
soundBtn_->setToggleMode(true);
|
||||||
|
soundBtn_->setStateBackgroundImage(soundOff, soundOn);
|
||||||
|
soundBtn_->setOn(g_SoundOpen);
|
||||||
|
soundBtn_->setAnchor(0.0f, 0.0f);
|
||||||
|
soundBtn_->setPosition(offsetX + 50.0f, offsetY + 50.0f);
|
||||||
|
soundBtn_->setOnStateChange([](bool isOn) {
|
||||||
|
g_SoundOpen = isOn;
|
||||||
|
AudioManager::instance().setEnabled(isOn);
|
||||||
|
});
|
||||||
|
addChild(soundBtn_);
|
||||||
|
}
|
||||||
|
|
||||||
// 计算游戏区域居中偏移(假设游戏区域是 640x480)
|
levelText_ = extra2d::Text::create("", font28_);
|
||||||
float gameWidth = 640.0f;
|
levelText_->setPosition(offsetX + 520.0f, offsetY + 30.0f);
|
||||||
float gameHeight = 480.0f;
|
levelText_->setTextColor(extra2d::Colors::White);
|
||||||
float offsetX = (screenW - gameWidth) / 2.0f;
|
addChild(levelText_);
|
||||||
float offsetY = (screenH - gameHeight) / 2.0f;
|
|
||||||
|
|
||||||
// 音效图标(左上角,与主界面一致)
|
stepText_ = extra2d::Text::create("", font20_);
|
||||||
auto soundOn = resources.loadTexture("assets/images/soundon.png");
|
stepText_->setPosition(offsetX + 520.0f, offsetY + 100.0f);
|
||||||
auto soundOff = resources.loadTexture("assets/images/soundoff.png");
|
stepText_->setTextColor(extra2d::Colors::White);
|
||||||
if (soundOn && soundOff) {
|
addChild(stepText_);
|
||||||
soundIcon_ = extra2d::Sprite::create(g_SoundOpen ? soundOn : soundOff);
|
|
||||||
soundIcon_->setPosition(offsetX + 50.0f, offsetY + 50.0f);
|
|
||||||
addChild(soundIcon_);
|
|
||||||
}
|
|
||||||
|
|
||||||
levelText_ = extra2d::Text::create("", font28_);
|
bestText_ = extra2d::Text::create("", font20_);
|
||||||
levelText_->setPosition(offsetX + 520.0f, offsetY + 30.0f);
|
bestText_->setPosition(offsetX + 520.0f, offsetY + 140.0f);
|
||||||
levelText_->setTextColor(extra2d::Colors::White);
|
bestText_->setTextColor(extra2d::Colors::White);
|
||||||
addChild(levelText_);
|
addChild(bestText_);
|
||||||
|
|
||||||
stepText_ = extra2d::Text::create("", font20_);
|
// 创建菜单文本(使用颜色变化指示选中)
|
||||||
stepText_->setPosition(offsetX + 520.0f, offsetY + 100.0f);
|
restartText_ = extra2d::Text::create("Y键重开", font20_);
|
||||||
stepText_->setTextColor(extra2d::Colors::White);
|
restartText_->setPosition(offsetX + 520.0f, offsetY + 290.0f);
|
||||||
addChild(stepText_);
|
addChild(restartText_);
|
||||||
|
|
||||||
bestText_ = extra2d::Text::create("", font20_);
|
soundToggleText_ = extra2d::Text::create("X键切换音效", font20_);
|
||||||
bestText_->setPosition(offsetX + 520.0f, offsetY + 140.0f);
|
soundToggleText_->setPosition(offsetX + 520.0f, offsetY + 330.0f);
|
||||||
bestText_->setTextColor(extra2d::Colors::White);
|
addChild(soundToggleText_);
|
||||||
addChild(bestText_);
|
|
||||||
|
|
||||||
// 创建菜单文本(使用颜色变化指示选中)
|
mapLayer_ = extra2d::makePtr<extra2d::Node>();
|
||||||
restartText_ = extra2d::Text::create("Y键重开", font20_);
|
mapLayer_->setAnchor(0.0f, 0.0f);
|
||||||
restartText_->setPosition(offsetX + 520.0f, offsetY + 290.0f);
|
mapLayer_->setPosition(0.0f, 0.0f);
|
||||||
addChild(restartText_);
|
addChild(mapLayer_);
|
||||||
|
|
||||||
soundToggleText_ = extra2d::Text::create("X键切换音效", font20_);
|
setLevel(level);
|
||||||
soundToggleText_->setPosition(offsetX + 520.0f, offsetY + 330.0f);
|
|
||||||
addChild(soundToggleText_);
|
|
||||||
|
|
||||||
mapLayer_ = extra2d::makePtr<extra2d::Node>();
|
|
||||||
mapLayer_->setAnchor(0.0f, 0.0f);
|
|
||||||
mapLayer_->setPosition(0.0f, 0.0f);
|
|
||||||
addChild(mapLayer_);
|
|
||||||
|
|
||||||
setLevel(level);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayScene::onEnter() {
|
void PlayScene::onEnter() {
|
||||||
Scene::onEnter();
|
BaseScene::onEnter();
|
||||||
updateSoundIcon();
|
if (soundBtn_) {
|
||||||
updateMenuColors();
|
soundBtn_->setOn(g_SoundOpen);
|
||||||
|
}
|
||||||
|
updateMenuColors();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 更新菜单颜色
|
||||||
|
*/
|
||||||
void PlayScene::updateMenuColors() {
|
void PlayScene::updateMenuColors() {
|
||||||
// 选中的项用红色,未选中的用白色
|
// 选中的项用红色,未选中的用白色
|
||||||
if (restartText_) {
|
if (restartText_) {
|
||||||
restartText_->setTextColor(menuIndex_ == 0 ? extra2d::Colors::Red : extra2d::Colors::White);
|
restartText_->setTextColor(menuIndex_ == 0 ? extra2d::Colors::Red
|
||||||
}
|
: extra2d::Colors::White);
|
||||||
if (soundToggleText_) {
|
}
|
||||||
soundToggleText_->setTextColor(menuIndex_ == 1 ? extra2d::Colors::Red : extra2d::Colors::White);
|
if (soundToggleText_) {
|
||||||
}
|
soundToggleText_->setTextColor(menuIndex_ == 1 ? extra2d::Colors::Red
|
||||||
|
: extra2d::Colors::White);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayScene::onUpdate(float dt) {
|
void PlayScene::onUpdate(float dt) {
|
||||||
Scene::onUpdate(dt);
|
BaseScene::onUpdate(dt);
|
||||||
|
|
||||||
auto& app = extra2d::Application::instance();
|
auto &app = extra2d::Application::instance();
|
||||||
auto& input = app.input();
|
auto &input = app.input();
|
||||||
|
|
||||||
// B 键返回主菜单
|
// B 键返回主菜单
|
||||||
if (input.isButtonPressed(extra2d::GamepadButton::B)) {
|
if (input.isButtonPressed(extra2d::GamepadButton::B)) {
|
||||||
app.scenes().replaceScene(
|
app.scenes().replaceScene(extra2d::makePtr<StartScene>(),
|
||||||
extra2d::makePtr<StartScene>(), extra2d::TransitionType::Fade, 0.2f);
|
extra2d::TransitionType::Fade, 0.5f);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Y 键重开
|
||||||
|
if (input.isButtonPressed(extra2d::GamepadButton::Y)) {
|
||||||
|
setLevel(g_CurrentLevel);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// X键直接切换音效(备用,按钮也可点击切换)
|
||||||
|
if (input.isButtonPressed(extra2d::GamepadButton::X)) {
|
||||||
|
g_SoundOpen = !g_SoundOpen;
|
||||||
|
AudioManager::instance().setEnabled(g_SoundOpen);
|
||||||
|
if (soundBtn_) {
|
||||||
|
soundBtn_->setOn(g_SoundOpen);
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Y 键重开
|
// A 键执行选中的菜单项
|
||||||
if (input.isButtonPressed(extra2d::GamepadButton::Y)) {
|
if (input.isButtonPressed(extra2d::GamepadButton::A)) {
|
||||||
setLevel(g_CurrentLevel);
|
executeMenuItem();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// X键直接切换音效
|
// 方向键移动
|
||||||
if (input.isButtonPressed(extra2d::GamepadButton::X)) {
|
if (input.isButtonPressed(extra2d::GamepadButton::DPadUp)) {
|
||||||
g_SoundOpen = !g_SoundOpen;
|
move(0, -1, 1);
|
||||||
AudioManager::instance().setEnabled(g_SoundOpen);
|
|
||||||
updateSoundIcon();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A 键执行选中的菜单项
|
|
||||||
if (input.isButtonPressed(extra2d::GamepadButton::A)) {
|
|
||||||
executeMenuItem();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 方向键移动
|
|
||||||
if (input.isButtonPressed(extra2d::GamepadButton::DPadUp)) {
|
|
||||||
move(0, -1, 1);
|
|
||||||
flush();
|
|
||||||
} else if (input.isButtonPressed(extra2d::GamepadButton::DPadDown)) {
|
|
||||||
move(0, 1, 2);
|
|
||||||
flush();
|
|
||||||
} else if (input.isButtonPressed(extra2d::GamepadButton::DPadLeft)) {
|
|
||||||
move(-1, 0, 3);
|
|
||||||
flush();
|
|
||||||
} else if (input.isButtonPressed(extra2d::GamepadButton::DPadRight)) {
|
|
||||||
move(1, 0, 4);
|
|
||||||
flush();
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否通关
|
|
||||||
for (int i = 0; i < map_.width; i++) {
|
|
||||||
for (int j = 0; j < map_.height; j++) {
|
|
||||||
Piece p = map_.value[j][i];
|
|
||||||
if (p.type == TYPE::Box && p.isPoint == false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gameOver();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayScene::executeMenuItem() {
|
|
||||||
switch (menuIndex_) {
|
|
||||||
case 0: // 重开
|
|
||||||
setLevel(g_CurrentLevel);
|
|
||||||
break;
|
|
||||||
case 1: // 切换音效
|
|
||||||
g_SoundOpen = !g_SoundOpen;
|
|
||||||
AudioManager::instance().setEnabled(g_SoundOpen);
|
|
||||||
updateSoundIcon();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayScene::updateSoundIcon() {
|
|
||||||
if (!soundIcon_) return;
|
|
||||||
|
|
||||||
auto& app = extra2d::Application::instance();
|
|
||||||
auto& resources = app.resources();
|
|
||||||
auto soundOn = resources.loadTexture("assets/images/soundon.png");
|
|
||||||
auto soundOff = resources.loadTexture("assets/images/soundoff.png");
|
|
||||||
|
|
||||||
if (soundOn && soundOff) {
|
|
||||||
soundIcon_->setTexture(g_SoundOpen ? soundOn : soundOff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayScene::flush() {
|
|
||||||
mapLayer_->removeAllChildren();
|
|
||||||
|
|
||||||
int tileW = texFloor_ ? texFloor_->getWidth() : 32;
|
|
||||||
int tileH = texFloor_ ? texFloor_->getHeight() : 32;
|
|
||||||
|
|
||||||
// 获取窗口尺寸,计算游戏区域居中偏移
|
|
||||||
auto& app = extra2d::Application::instance();
|
|
||||||
float screenW = static_cast<float>(app.getConfig().width);
|
|
||||||
float screenH = static_cast<float>(app.getConfig().height);
|
|
||||||
float gameWidth = 640.0f;
|
|
||||||
float gameHeight = 480.0f;
|
|
||||||
float baseOffsetX = (screenW - gameWidth) / 2.0f;
|
|
||||||
float baseOffsetY = (screenH - gameHeight) / 2.0f;
|
|
||||||
|
|
||||||
// 在 12x12 网格中居中地图
|
|
||||||
float mapOffsetX = static_cast<float>((12 - map_.width) / 2) * tileW;
|
|
||||||
float mapOffsetY = static_cast<float>((12 - map_.height) / 2) * tileH;
|
|
||||||
|
|
||||||
float offsetX = baseOffsetX + mapOffsetX;
|
|
||||||
float offsetY = baseOffsetY + mapOffsetY;
|
|
||||||
|
|
||||||
for (int i = 0; i < map_.width; i++) {
|
|
||||||
for (int j = 0; j < map_.height; j++) {
|
|
||||||
Piece piece = map_.value[j][i];
|
|
||||||
|
|
||||||
extra2d::Ptr<extra2d::Texture> tex;
|
|
||||||
|
|
||||||
if (piece.type == TYPE::Wall) {
|
|
||||||
tex = texWall_;
|
|
||||||
} else if (piece.type == TYPE::Ground && piece.isPoint) {
|
|
||||||
tex = texPoint_;
|
|
||||||
} else if (piece.type == TYPE::Ground) {
|
|
||||||
tex = texFloor_;
|
|
||||||
} else if (piece.type == TYPE::Box && piece.isPoint) {
|
|
||||||
tex = texBoxInPoint_;
|
|
||||||
} else if (piece.type == TYPE::Box) {
|
|
||||||
tex = texBox_;
|
|
||||||
} else if (piece.type == TYPE::Man && g_Pushing) {
|
|
||||||
tex = texManPush_[g_Direct];
|
|
||||||
} else if (piece.type == TYPE::Man) {
|
|
||||||
tex = texMan_[g_Direct];
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tex) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto sprite = extra2d::Sprite::create(tex);
|
|
||||||
sprite->setAnchor(0.0f, 0.0f);
|
|
||||||
sprite->setPosition(offsetX + static_cast<float>(i * tileW),
|
|
||||||
offsetY + static_cast<float>(j * tileH));
|
|
||||||
mapLayer_->addChild(sprite);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PlayScene::setLevel(int level) {
|
|
||||||
g_CurrentLevel = level;
|
|
||||||
saveCurrentLevel(g_CurrentLevel);
|
|
||||||
|
|
||||||
if (levelText_) {
|
|
||||||
levelText_->setText("第" + std::to_string(level) + "关");
|
|
||||||
}
|
|
||||||
|
|
||||||
setStep(0);
|
|
||||||
|
|
||||||
int bestStep = loadBestStep(level, 0);
|
|
||||||
if (bestText_) {
|
|
||||||
if (bestStep != 0) {
|
|
||||||
bestText_->setText("最佳" + std::to_string(bestStep) + "步");
|
|
||||||
} else {
|
|
||||||
bestText_->setText("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 深拷贝地图数据
|
|
||||||
Map& sourceMap = g_Maps[level - 1];
|
|
||||||
map_.width = sourceMap.width;
|
|
||||||
map_.height = sourceMap.height;
|
|
||||||
map_.roleX = sourceMap.roleX;
|
|
||||||
map_.roleY = sourceMap.roleY;
|
|
||||||
for (int i = 0; i < 12; i++) {
|
|
||||||
for (int j = 0; j < 12; j++) {
|
|
||||||
map_.value[i][j] = sourceMap.value[i][j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g_Direct = 2;
|
|
||||||
g_Pushing = false;
|
|
||||||
flush();
|
flush();
|
||||||
|
} else if (input.isButtonPressed(extra2d::GamepadButton::DPadDown)) {
|
||||||
|
move(0, 1, 2);
|
||||||
|
flush();
|
||||||
|
} else if (input.isButtonPressed(extra2d::GamepadButton::DPadLeft)) {
|
||||||
|
move(-1, 0, 3);
|
||||||
|
flush();
|
||||||
|
} else if (input.isButtonPressed(extra2d::GamepadButton::DPadRight)) {
|
||||||
|
move(1, 0, 4);
|
||||||
|
flush();
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否通关
|
||||||
|
for (int i = 0; i < map_.width; i++) {
|
||||||
|
for (int j = 0; j < map_.height; j++) {
|
||||||
|
Piece p = map_.value[j][i];
|
||||||
|
if (p.type == TYPE::Box && p.isPoint == false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gameOver();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayScene::setStep(int step) {
|
/**
|
||||||
step_ = step;
|
* @brief 执行选中的菜单项
|
||||||
if (stepText_) {
|
*/
|
||||||
stepText_->setText("当前" + std::to_string(step) + "步");
|
void PlayScene::executeMenuItem() {
|
||||||
|
switch (menuIndex_) {
|
||||||
|
case 0: // 重开
|
||||||
|
setLevel(g_CurrentLevel);
|
||||||
|
break;
|
||||||
|
case 1: // 切换音效
|
||||||
|
g_SoundOpen = !g_SoundOpen;
|
||||||
|
AudioManager::instance().setEnabled(g_SoundOpen);
|
||||||
|
if (soundBtn_) {
|
||||||
|
soundBtn_->setOn(g_SoundOpen);
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayScene::move(int dx, int dy, int direct) {
|
/**
|
||||||
int targetX = dx + map_.roleX;
|
* @brief 刷新地图显示
|
||||||
int targetY = dy + map_.roleY;
|
*/
|
||||||
g_Direct = direct;
|
void PlayScene::flush() {
|
||||||
|
mapLayer_->removeAllChildren();
|
||||||
|
|
||||||
if (targetX < 0 || targetX >= map_.width || targetY < 0 || targetY >= map_.height) {
|
int tileW = texFloor_ ? texFloor_->getWidth() : 32;
|
||||||
return;
|
int tileH = texFloor_ ? texFloor_->getHeight() : 32;
|
||||||
|
|
||||||
|
// 使用游戏逻辑分辨率
|
||||||
|
float gameWidth = GAME_WIDTH;
|
||||||
|
float gameHeight = GAME_HEIGHT;
|
||||||
|
float baseOffsetX = 0.0f;
|
||||||
|
float baseOffsetY = 0.0f;
|
||||||
|
|
||||||
|
// 在 12x12 网格中居中地图
|
||||||
|
float mapOffsetX = static_cast<float>((12.0f - map_.width) / 2.0f) * tileW;
|
||||||
|
float mapOffsetY = static_cast<float>((12.0f - map_.height) / 2.0f) * tileH;
|
||||||
|
|
||||||
|
float offsetX = baseOffsetX + mapOffsetX;
|
||||||
|
float offsetY = baseOffsetY + mapOffsetY;
|
||||||
|
|
||||||
|
for (int i = 0; i < map_.width; i++) {
|
||||||
|
for (int j = 0; j < map_.height; j++) {
|
||||||
|
Piece piece = map_.value[j][i];
|
||||||
|
|
||||||
|
extra2d::Ptr<extra2d::Texture> tex;
|
||||||
|
|
||||||
|
if (piece.type == TYPE::Wall) {
|
||||||
|
tex = texWall_;
|
||||||
|
} else if (piece.type == TYPE::Ground && piece.isPoint) {
|
||||||
|
tex = texPoint_;
|
||||||
|
} else if (piece.type == TYPE::Ground) {
|
||||||
|
tex = texFloor_;
|
||||||
|
} else if (piece.type == TYPE::Box && piece.isPoint) {
|
||||||
|
tex = texBoxInPoint_;
|
||||||
|
} else if (piece.type == TYPE::Box) {
|
||||||
|
tex = texBox_;
|
||||||
|
} else if (piece.type == TYPE::Man && g_Pushing) {
|
||||||
|
tex = texManPush_[g_Direct];
|
||||||
|
} else if (piece.type == TYPE::Man) {
|
||||||
|
tex = texMan_[g_Direct];
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tex) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto sprite = extra2d::Sprite::create(tex);
|
||||||
|
sprite->setAnchor(0.0f, 0.0f);
|
||||||
|
sprite->setPosition(offsetX + static_cast<float>(i * tileW),
|
||||||
|
offsetY + static_cast<float>(j * tileH));
|
||||||
|
mapLayer_->addChild(sprite);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (map_.value[targetY][targetX].type == TYPE::Wall) {
|
/**
|
||||||
return;
|
* @brief 设置关卡
|
||||||
}
|
* @param level 关卡编号
|
||||||
|
*/
|
||||||
|
void PlayScene::setLevel(int level) {
|
||||||
|
g_CurrentLevel = level;
|
||||||
|
saveCurrentLevel(g_CurrentLevel);
|
||||||
|
|
||||||
if (map_.value[targetY][targetX].type == TYPE::Ground) {
|
if (levelText_) {
|
||||||
g_Pushing = false;
|
levelText_->setText("第" + std::to_string(level) + "关");
|
||||||
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
|
}
|
||||||
map_.value[targetY][targetX].type = TYPE::Man;
|
|
||||||
AudioManager::instance().playManMove();
|
|
||||||
} else if (map_.value[targetY][targetX].type == TYPE::Box) {
|
|
||||||
g_Pushing = true;
|
|
||||||
|
|
||||||
int boxX = 0;
|
setStep(0);
|
||||||
int boxY = 0;
|
|
||||||
switch (g_Direct) {
|
|
||||||
case 1:
|
|
||||||
boxX = targetX;
|
|
||||||
boxY = targetY - 1;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
boxX = targetX;
|
|
||||||
boxY = targetY + 1;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
boxX = targetX - 1;
|
|
||||||
boxY = targetY;
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
boxX = targetX + 1;
|
|
||||||
boxY = targetY;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (boxX < 0 || boxX >= map_.width || boxY < 0 || boxY >= map_.height) {
|
int bestStep = loadBestStep(level, 0);
|
||||||
return;
|
if (bestText_) {
|
||||||
}
|
if (bestStep != 0) {
|
||||||
|
bestText_->setText("最佳" + std::to_string(bestStep) + "步");
|
||||||
if (map_.value[boxY][boxX].type == TYPE::Wall || map_.value[boxY][boxX].type == TYPE::Box) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
map_.value[boxY][boxX].type = TYPE::Box;
|
|
||||||
map_.value[targetY][targetX].type = TYPE::Man;
|
|
||||||
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
|
|
||||||
|
|
||||||
AudioManager::instance().playBoxMove();
|
|
||||||
} else {
|
} else {
|
||||||
return;
|
bestText_->setText("");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
map_.roleX = targetX;
|
// 深拷贝地图数据
|
||||||
map_.roleY = targetY;
|
Map &sourceMap = g_Maps[level - 1];
|
||||||
setStep(step_ + 1);
|
map_.width = sourceMap.width;
|
||||||
|
map_.height = sourceMap.height;
|
||||||
|
map_.roleX = sourceMap.roleX;
|
||||||
|
map_.roleY = sourceMap.roleY;
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
for (int j = 0; j < 12; j++) {
|
||||||
|
map_.value[i][j] = sourceMap.value[i][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_Direct = 2;
|
||||||
|
g_Pushing = false;
|
||||||
|
flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置步数
|
||||||
|
* @param step 步数
|
||||||
|
*/
|
||||||
|
void PlayScene::setStep(int step) {
|
||||||
|
step_ = step;
|
||||||
|
if (stepText_) {
|
||||||
|
stepText_->setText("当前" + std::to_string(step) + "步");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 移动玩家
|
||||||
|
* @param dx X方向偏移
|
||||||
|
* @param dy Y方向偏移
|
||||||
|
* @param direct 方向(1=上,2=下,3=左,4=右)
|
||||||
|
*/
|
||||||
|
void PlayScene::move(int dx, int dy, int direct) {
|
||||||
|
int targetX = dx + map_.roleX;
|
||||||
|
int targetY = dy + map_.roleY;
|
||||||
|
g_Direct = direct;
|
||||||
|
|
||||||
|
if (targetX < 0 || targetX >= map_.width || targetY < 0 ||
|
||||||
|
targetY >= map_.height) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map_.value[targetY][targetX].type == TYPE::Wall) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map_.value[targetY][targetX].type == TYPE::Ground) {
|
||||||
|
g_Pushing = false;
|
||||||
|
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
|
||||||
|
map_.value[targetY][targetX].type = TYPE::Man;
|
||||||
|
AudioManager::instance().playManMove();
|
||||||
|
} else if (map_.value[targetY][targetX].type == TYPE::Box) {
|
||||||
|
g_Pushing = true;
|
||||||
|
|
||||||
|
int boxX = 0;
|
||||||
|
int boxY = 0;
|
||||||
|
switch (g_Direct) {
|
||||||
|
case 1:
|
||||||
|
boxX = targetX;
|
||||||
|
boxY = targetY - 1;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
boxX = targetX;
|
||||||
|
boxY = targetY + 1;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
boxX = targetX - 1;
|
||||||
|
boxY = targetY;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
boxX = targetX + 1;
|
||||||
|
boxY = targetY;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (boxX < 0 || boxX >= map_.width || boxY < 0 || boxY >= map_.height) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map_.value[boxY][boxX].type == TYPE::Wall ||
|
||||||
|
map_.value[boxY][boxX].type == TYPE::Box) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
map_.value[boxY][boxX].type = TYPE::Box;
|
||||||
|
map_.value[targetY][targetX].type = TYPE::Man;
|
||||||
|
map_.value[map_.roleY][map_.roleX].type = TYPE::Ground;
|
||||||
|
|
||||||
|
AudioManager::instance().playBoxMove();
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
map_.roleX = targetX;
|
||||||
|
map_.roleY = targetY;
|
||||||
|
setStep(step_ + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 游戏通关
|
||||||
|
*/
|
||||||
void PlayScene::gameOver() {
|
void PlayScene::gameOver() {
|
||||||
int bestStep = loadBestStep(g_CurrentLevel, 0);
|
int bestStep = loadBestStep(g_CurrentLevel, 0);
|
||||||
if (bestStep == 0 || step_ < bestStep) {
|
if (bestStep == 0 || step_ < bestStep) {
|
||||||
saveBestStep(g_CurrentLevel, step_);
|
saveBestStep(g_CurrentLevel, step_);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (g_CurrentLevel == MAX_LEVEL) {
|
if (g_CurrentLevel == MAX_LEVEL) {
|
||||||
extra2d::Application::instance().scenes().pushScene(extra2d::makePtr<SuccessScene>(),
|
extra2d::Application::instance().scenes().pushScene(
|
||||||
extra2d::TransitionType::Fade, 0.25f);
|
extra2d::makePtr<SuccessScene>(), extra2d::TransitionType::Fade, 0.5f);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setLevel(g_CurrentLevel + 1);
|
setLevel(g_CurrentLevel + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace pushbox
|
} // namespace pushbox
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,76 @@
|
||||||
|
// ============================================================================
|
||||||
|
// PlayScene.h - Push Box 游戏场景
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "BaseScene.h"
|
||||||
#include "data.h"
|
#include "data.h"
|
||||||
#include <extra2d/extra2d.h>
|
#include <extra2d/extra2d.h>
|
||||||
|
|
||||||
namespace pushbox {
|
namespace pushbox {
|
||||||
|
|
||||||
class PlayScene : public extra2d::Scene {
|
/**
|
||||||
|
* @brief Push Box 游戏场景
|
||||||
|
*/
|
||||||
|
class PlayScene : public BaseScene {
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 构造函数
|
||||||
|
* @param level 关卡编号
|
||||||
|
*/
|
||||||
explicit PlayScene(int level);
|
explicit PlayScene(int level);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 场景进入时调用
|
||||||
|
*/
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 每帧更新
|
||||||
|
* @param dt 帧间隔时间
|
||||||
|
*/
|
||||||
void onUpdate(float dt) override;
|
void onUpdate(float dt) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/**
|
||||||
|
* @brief 更新菜单颜色
|
||||||
|
*/
|
||||||
void updateMenuColors();
|
void updateMenuColors();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 执行选中的菜单项
|
||||||
|
*/
|
||||||
void executeMenuItem();
|
void executeMenuItem();
|
||||||
void updateSoundIcon();
|
|
||||||
|
/**
|
||||||
|
* @brief 刷新地图显示
|
||||||
|
*/
|
||||||
void flush();
|
void flush();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置关卡
|
||||||
|
* @param level 关卡编号
|
||||||
|
*/
|
||||||
void setLevel(int level);
|
void setLevel(int level);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置步数
|
||||||
|
* @param step 步数
|
||||||
|
*/
|
||||||
void setStep(int step);
|
void setStep(int step);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 移动玩家
|
||||||
|
* @param dx X方向偏移
|
||||||
|
* @param dy Y方向偏移
|
||||||
|
* @param direct 方向(1=上,2=下,3=左,4=右)
|
||||||
|
*/
|
||||||
void move(int dx, int dy, int direct);
|
void move(int dx, int dy, int direct);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 游戏通关
|
||||||
|
*/
|
||||||
void gameOver();
|
void gameOver();
|
||||||
|
|
||||||
int step_ = 0;
|
int step_ = 0;
|
||||||
|
|
@ -36,7 +87,7 @@ private:
|
||||||
extra2d::Ptr<extra2d::Text> soundToggleText_;
|
extra2d::Ptr<extra2d::Text> soundToggleText_;
|
||||||
extra2d::Ptr<extra2d::Node> mapLayer_;
|
extra2d::Ptr<extra2d::Node> mapLayer_;
|
||||||
|
|
||||||
extra2d::Ptr<extra2d::Sprite> soundIcon_;
|
extra2d::Ptr<extra2d::Button> soundBtn_;
|
||||||
|
|
||||||
extra2d::Ptr<extra2d::Texture> texWall_;
|
extra2d::Ptr<extra2d::Texture> texWall_;
|
||||||
extra2d::Ptr<extra2d::Texture> texPoint_;
|
extra2d::Ptr<extra2d::Texture> texPoint_;
|
||||||
|
|
|
||||||
|
|
@ -1,210 +1,227 @@
|
||||||
|
// ============================================================================
|
||||||
|
// StartScene.cpp - Push Box 开始场景实现
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
#include "StartScene.h"
|
#include "StartScene.h"
|
||||||
|
|
||||||
|
#include "PlayScene.h"
|
||||||
#include "audio_manager.h"
|
#include "audio_manager.h"
|
||||||
#include "data.h"
|
#include "data.h"
|
||||||
#include "PlayScene.h"
|
|
||||||
#include <extra2d/extra2d.h>
|
#include <extra2d/extra2d.h>
|
||||||
|
|
||||||
namespace pushbox {
|
namespace pushbox {
|
||||||
|
|
||||||
StartScene::StartScene() {
|
StartScene::StartScene() : BaseScene() {
|
||||||
auto& app = extra2d::Application::instance();
|
// BaseScene 已处理视口设置
|
||||||
auto& config = app.getConfig();
|
|
||||||
setViewportSize(static_cast<float>(config.width), static_cast<float>(config.height));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 加载菜单字体
|
||||||
|
*/
|
||||||
static extra2d::Ptr<extra2d::FontAtlas> loadMenuFont() {
|
static extra2d::Ptr<extra2d::FontAtlas> loadMenuFont() {
|
||||||
auto& resources = extra2d::Application::instance().resources();
|
auto &resources = extra2d::Application::instance().resources();
|
||||||
auto font = resources.loadFont("assets/font.ttf", 28,true);
|
auto font = resources.loadFont("assets/font.ttf", 28, true);
|
||||||
return font;
|
return font;
|
||||||
}
|
}
|
||||||
|
|
||||||
void StartScene::onEnter() {
|
void StartScene::onEnter() {
|
||||||
Scene::onEnter();
|
BaseScene::onEnter();
|
||||||
|
|
||||||
auto& app = extra2d::Application::instance();
|
auto &app = extra2d::Application::instance();
|
||||||
auto& resources = app.resources();
|
auto &resources = app.resources();
|
||||||
setBackgroundColor(extra2d::Colors::Black);
|
|
||||||
|
|
||||||
if (getChildren().empty()) {
|
if (getChildren().empty()) {
|
||||||
|
// 使用游戏逻辑分辨率
|
||||||
|
float screenW = GAME_WIDTH;
|
||||||
|
float screenH = GAME_HEIGHT;
|
||||||
|
|
||||||
float screenW = static_cast<float>(app.getConfig().width);
|
auto bgTex = resources.loadTexture("assets/images/start.jpg");
|
||||||
float screenH = static_cast<float>(app.getConfig().height);
|
if (bgTex) {
|
||||||
|
auto background = extra2d::Sprite::create(bgTex);
|
||||||
|
float bgWidth = static_cast<float>(bgTex->getWidth());
|
||||||
|
float bgHeight = static_cast<float>(bgTex->getHeight());
|
||||||
|
float offsetX = (screenW - bgWidth) / 2.0f;
|
||||||
|
float offsetY = (screenH - bgHeight) / 2.0f;
|
||||||
|
|
||||||
auto bgTex = resources.loadTexture("assets/images/start.jpg");
|
background->setAnchor(0.0f, 0.0f);
|
||||||
if (bgTex) {
|
background->setPosition(offsetX, offsetY);
|
||||||
auto background = extra2d::Sprite::create(bgTex);
|
addChild(background);
|
||||||
float bgWidth = static_cast<float>(bgTex->getWidth());
|
|
||||||
float bgHeight = static_cast<float>(bgTex->getHeight());
|
|
||||||
float offsetX = (screenW - bgWidth) / 2.0f;
|
|
||||||
float offsetY = (screenH - bgHeight) / 2.0f;
|
|
||||||
|
|
||||||
background->setAnchor(0.0f, 0.0f);
|
|
||||||
background->setPosition(offsetX, offsetY);
|
|
||||||
addChild(background);
|
|
||||||
|
|
||||||
float centerX = screenW / 2.0f;
|
|
||||||
|
|
||||||
font_ = loadMenuFont();
|
|
||||||
if (!font_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建菜单按钮(使用 Button 实现文本居中)
|
float centerX = screenW / 2.0f;
|
||||||
// 设置按钮锚点为中心点,位置设为屏幕中心,实现真正的居中
|
|
||||||
startBtn_ = extra2d::Button::create();
|
|
||||||
startBtn_->setFont(font_);
|
|
||||||
startBtn_->setText("新游戏");
|
|
||||||
startBtn_->setTextColor(extra2d::Colors::Black);
|
|
||||||
startBtn_->setBackgroundColor(extra2d::Colors::Transparent,
|
|
||||||
extra2d::Colors::Transparent,
|
|
||||||
extra2d::Colors::Transparent);
|
|
||||||
startBtn_->setBorder(extra2d::Colors::Transparent, 0.0f);
|
|
||||||
startBtn_->setPadding(extra2d::Vec2(0.0f, 0.0f));
|
|
||||||
startBtn_->setCustomSize(200.0f, 40.0f);
|
|
||||||
startBtn_->setAnchor(0.5f, 0.5f);
|
|
||||||
startBtn_->setPosition(centerX, offsetY + 260.0f);
|
|
||||||
addChild(startBtn_);
|
|
||||||
|
|
||||||
resumeBtn_ = extra2d::Button::create();
|
font_ = loadMenuFont();
|
||||||
resumeBtn_->setFont(font_);
|
if (!font_) {
|
||||||
resumeBtn_->setText("继续关卡");
|
return;
|
||||||
resumeBtn_->setTextColor(extra2d::Colors::Black);
|
}
|
||||||
resumeBtn_->setBackgroundColor(extra2d::Colors::Transparent,
|
|
||||||
extra2d::Colors::Transparent,
|
|
||||||
extra2d::Colors::Transparent);
|
|
||||||
resumeBtn_->setBorder(extra2d::Colors::Transparent, 0.0f);
|
|
||||||
resumeBtn_->setPadding(extra2d::Vec2(0.0f, 0.0f));
|
|
||||||
resumeBtn_->setCustomSize(200.0f, 40.0f);
|
|
||||||
resumeBtn_->setAnchor(0.5f, 0.5f);
|
|
||||||
resumeBtn_->setPosition(centerX, offsetY + 300.0f);
|
|
||||||
addChild(resumeBtn_);
|
|
||||||
|
|
||||||
exitBtn_ = extra2d::Button::create();
|
// 创建菜单按钮(使用 Button 实现文本居中)
|
||||||
exitBtn_->setFont(font_);
|
startBtn_ = extra2d::Button::create();
|
||||||
exitBtn_->setText("退出");
|
startBtn_->setFont(font_);
|
||||||
exitBtn_->setTextColor(extra2d::Colors::Black);
|
startBtn_->setText("新游戏");
|
||||||
exitBtn_->setBackgroundColor(extra2d::Colors::Transparent,
|
startBtn_->setTextColor(extra2d::Colors::Black);
|
||||||
extra2d::Colors::Transparent,
|
startBtn_->setBackgroundColor(extra2d::Colors::Transparent,
|
||||||
extra2d::Colors::Transparent);
|
extra2d::Colors::Transparent,
|
||||||
exitBtn_->setBorder(extra2d::Colors::Transparent, 0.0f);
|
extra2d::Colors::Transparent);
|
||||||
exitBtn_->setPadding(extra2d::Vec2(0.0f, 0.0f));
|
startBtn_->setBorder(extra2d::Colors::Transparent, 0.0f);
|
||||||
exitBtn_->setCustomSize(200.0f, 40.0f);
|
startBtn_->setPadding(extra2d::Vec2(0.0f, 0.0f));
|
||||||
exitBtn_->setAnchor(0.5f, 0.5f);
|
startBtn_->setCustomSize(200.0f, 40.0f);
|
||||||
exitBtn_->setPosition(centerX, offsetY + 340.0f);
|
startBtn_->setAnchor(0.5f, 0.5f);
|
||||||
addChild(exitBtn_);
|
startBtn_->setPosition(centerX, offsetY + 260.0f);
|
||||||
|
addChild(startBtn_);
|
||||||
|
|
||||||
// 音效开关图标(相对于背景图左上角)
|
resumeBtn_ = extra2d::Button::create();
|
||||||
auto soundOn = resources.loadTexture("assets/images/soundon.png");
|
resumeBtn_->setFont(font_);
|
||||||
auto soundOff = resources.loadTexture("assets/images/soundoff.png");
|
resumeBtn_->setText("继续关卡");
|
||||||
if (soundOn && soundOff) {
|
resumeBtn_->setTextColor(extra2d::Colors::Black);
|
||||||
soundIcon_ = extra2d::Sprite::create(g_SoundOpen ? soundOn : soundOff);
|
resumeBtn_->setBackgroundColor(extra2d::Colors::Transparent,
|
||||||
soundIcon_->setPosition(offsetX + 50.0f, offsetY + 50.0f);
|
extra2d::Colors::Transparent,
|
||||||
addChild(soundIcon_);
|
extra2d::Colors::Transparent);
|
||||||
}
|
resumeBtn_->setBorder(extra2d::Colors::Transparent, 0.0f);
|
||||||
}
|
resumeBtn_->setPadding(extra2d::Vec2(0.0f, 0.0f));
|
||||||
|
resumeBtn_->setCustomSize(200.0f, 40.0f);
|
||||||
|
resumeBtn_->setAnchor(0.5f, 0.5f);
|
||||||
|
resumeBtn_->setPosition(centerX, offsetY + 300.0f);
|
||||||
|
addChild(resumeBtn_);
|
||||||
|
|
||||||
|
exitBtn_ = extra2d::Button::create();
|
||||||
|
exitBtn_->setFont(font_);
|
||||||
|
exitBtn_->setText("退出");
|
||||||
|
exitBtn_->setTextColor(extra2d::Colors::Black);
|
||||||
|
exitBtn_->setBackgroundColor(extra2d::Colors::Transparent,
|
||||||
|
extra2d::Colors::Transparent,
|
||||||
|
extra2d::Colors::Transparent);
|
||||||
|
exitBtn_->setBorder(extra2d::Colors::Transparent, 0.0f);
|
||||||
|
exitBtn_->setPadding(extra2d::Vec2(0.0f, 0.0f));
|
||||||
|
exitBtn_->setCustomSize(200.0f, 40.0f);
|
||||||
|
exitBtn_->setAnchor(0.5f, 0.5f);
|
||||||
|
exitBtn_->setPosition(centerX, offsetY + 340.0f);
|
||||||
|
addChild(exitBtn_);
|
||||||
|
|
||||||
|
// 音效开关按钮(使用 Button 的切换模式)
|
||||||
|
auto soundOn = resources.loadTexture("assets/images/soundon.png");
|
||||||
|
auto soundOff = resources.loadTexture("assets/images/soundoff.png");
|
||||||
|
if (soundOn && soundOff) {
|
||||||
|
soundBtn_ = extra2d::Button::create();
|
||||||
|
soundBtn_->setToggleMode(true);
|
||||||
|
soundBtn_->setStateBackgroundImage(soundOff, soundOn);
|
||||||
|
soundBtn_->setOn(g_SoundOpen);
|
||||||
|
soundBtn_->setAnchor(0.0f, 0.0f);
|
||||||
|
soundBtn_->setPosition(offsetX + 50.0f, offsetY + 50.0f);
|
||||||
|
soundBtn_->setOnStateChange([](bool isOn) {
|
||||||
|
g_SoundOpen = isOn;
|
||||||
|
AudioManager::instance().setEnabled(isOn);
|
||||||
|
});
|
||||||
|
addChild(soundBtn_);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 始终有3个菜单项
|
// 始终有3个菜单项
|
||||||
menuCount_ = 3;
|
menuCount_ = 3;
|
||||||
updateMenuColors();
|
updateMenuColors();
|
||||||
}
|
}
|
||||||
|
|
||||||
void StartScene::onUpdate(float dt) {
|
void StartScene::onUpdate(float dt) {
|
||||||
Scene::onUpdate(dt);
|
BaseScene::onUpdate(dt);
|
||||||
|
|
||||||
auto& app = extra2d::Application::instance();
|
auto &app = extra2d::Application::instance();
|
||||||
auto& input = app.input();
|
auto &input = app.input();
|
||||||
|
|
||||||
// 方向键上下切换选择
|
// 方向键上下切换选择
|
||||||
if (input.isButtonPressed(extra2d::GamepadButton::DPadUp)) {
|
if (input.isButtonPressed(extra2d::GamepadButton::DPadUp)) {
|
||||||
selectedIndex_ = (selectedIndex_ - 1 + menuCount_) % menuCount_;
|
selectedIndex_ = (selectedIndex_ - 1 + menuCount_) % menuCount_;
|
||||||
updateMenuColors();
|
updateMenuColors();
|
||||||
} else if (input.isButtonPressed(extra2d::GamepadButton::DPadDown)) {
|
} else if (input.isButtonPressed(extra2d::GamepadButton::DPadDown)) {
|
||||||
selectedIndex_ = (selectedIndex_ + 1) % menuCount_;
|
selectedIndex_ = (selectedIndex_ + 1) % menuCount_;
|
||||||
updateMenuColors();
|
updateMenuColors();
|
||||||
}
|
}
|
||||||
|
|
||||||
// A键确认
|
// A键确认
|
||||||
if (input.isButtonPressed(extra2d::GamepadButton::A)) {
|
if (input.isButtonPressed(extra2d::GamepadButton::A)) {
|
||||||
executeMenuItem();
|
executeMenuItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
// X键切换音效
|
// X键切换音效(备用,按钮也可点击切换)
|
||||||
if (input.isButtonPressed(extra2d::GamepadButton::X)) {
|
if (input.isButtonPressed(extra2d::GamepadButton::X)) {
|
||||||
g_SoundOpen = !g_SoundOpen;
|
g_SoundOpen = !g_SoundOpen;
|
||||||
AudioManager::instance().setEnabled(g_SoundOpen);
|
AudioManager::instance().setEnabled(g_SoundOpen);
|
||||||
updateSoundIcon();
|
if (soundBtn_) {
|
||||||
|
soundBtn_->setOn(g_SoundOpen);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 更新菜单颜色
|
||||||
|
*/
|
||||||
void StartScene::updateMenuColors() {
|
void StartScene::updateMenuColors() {
|
||||||
// 根据选中状态更新按钮文本颜色
|
// 根据选中状态更新按钮文本颜色
|
||||||
// 选中的项用红色,未选中的用黑色,禁用的项用深灰色
|
// 选中的项用红色,未选中的用黑色,禁用的项用深灰色
|
||||||
|
|
||||||
if (startBtn_) {
|
if (startBtn_) {
|
||||||
startBtn_->setTextColor(selectedIndex_ == 0 ? extra2d::Colors::Red : extra2d::Colors::Black);
|
startBtn_->setTextColor(selectedIndex_ == 0 ? extra2d::Colors::Red
|
||||||
}
|
: extra2d::Colors::Black);
|
||||||
|
}
|
||||||
if (resumeBtn_) {
|
|
||||||
// "继续关卡"始终显示,但当 g_CurrentLevel == 1 时禁用(深灰色)
|
if (resumeBtn_) {
|
||||||
if (g_CurrentLevel > 1) {
|
// "继续关卡"始终显示,但当 g_CurrentLevel == 1 时禁用(深灰色)
|
||||||
// 可用状态:选中为红色,未选中为黑色
|
if (g_CurrentLevel > 1) {
|
||||||
resumeBtn_->setTextColor(selectedIndex_ == 1 ? extra2d::Colors::Red : extra2d::Colors::Black);
|
// 可用状态:选中为红色,未选中为黑色
|
||||||
} else {
|
resumeBtn_->setTextColor(selectedIndex_ == 1 ? extra2d::Colors::Red
|
||||||
// 禁用状态:深灰色 (RGB: 80, 80, 80)
|
: extra2d::Colors::Black);
|
||||||
resumeBtn_->setTextColor(extra2d::Color(80, 80, 80, 255));
|
} else {
|
||||||
}
|
// 禁用状态:深灰色 (RGB: 80, 80, 80)
|
||||||
}
|
resumeBtn_->setTextColor(extra2d::Color(80, 80, 80, 255));
|
||||||
|
|
||||||
if (exitBtn_) {
|
|
||||||
exitBtn_->setTextColor(selectedIndex_ == 2 ? extra2d::Colors::Red : extra2d::Colors::Black);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exitBtn_) {
|
||||||
|
exitBtn_->setTextColor(selectedIndex_ == 2 ? extra2d::Colors::Red
|
||||||
|
: extra2d::Colors::Black);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 执行选中的菜单项
|
||||||
|
*/
|
||||||
void StartScene::executeMenuItem() {
|
void StartScene::executeMenuItem() {
|
||||||
// 始终有3个选项,但"继续关卡"(索引1)在 g_CurrentLevel == 1 时禁用
|
// 始终有3个选项,但"继续关卡"(索引1)在 g_CurrentLevel == 1 时禁用
|
||||||
switch (selectedIndex_) {
|
switch (selectedIndex_) {
|
||||||
case 0:
|
case 0:
|
||||||
startNewGame();
|
startNewGame();
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
// 只有当 g_CurrentLevel > 1 时才能选择"继续关卡"
|
// 只有当 g_CurrentLevel > 1 时才能选择"继续关卡"
|
||||||
if (g_CurrentLevel > 1) {
|
if (g_CurrentLevel > 1) {
|
||||||
continueGame();
|
continueGame();
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
exitGame();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
exitGame();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 开始新游戏
|
||||||
|
*/
|
||||||
void StartScene::startNewGame() {
|
void StartScene::startNewGame() {
|
||||||
extra2d::Application::instance().scenes().replaceScene(
|
extra2d::Application::instance().scenes().replaceScene(
|
||||||
extra2d::makePtr<PlayScene>(1), extra2d::TransitionType::Fade, 0.25f);
|
extra2d::makePtr<PlayScene>(1), extra2d::TransitionType::Fade, 0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 继续游戏
|
||||||
|
*/
|
||||||
void StartScene::continueGame() {
|
void StartScene::continueGame() {
|
||||||
extra2d::Application::instance().scenes().replaceScene(
|
extra2d::Application::instance().scenes().replaceScene(
|
||||||
extra2d::makePtr<PlayScene>(g_CurrentLevel), extra2d::TransitionType::Fade, 0.25f);
|
extra2d::makePtr<PlayScene>(g_CurrentLevel),
|
||||||
|
extra2d::TransitionType::Fade, 0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
void StartScene::exitGame() {
|
/**
|
||||||
extra2d::Application::instance().quit();
|
* @brief 退出游戏
|
||||||
}
|
*/
|
||||||
|
void StartScene::exitGame() { extra2d::Application::instance().quit(); }
|
||||||
|
|
||||||
} // namespace pushbox
|
} // namespace pushbox
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,66 @@
|
||||||
|
// ============================================================================
|
||||||
|
// StartScene.h - Push Box 开始场景
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "BaseScene.h"
|
||||||
#include <extra2d/extra2d.h>
|
#include <extra2d/extra2d.h>
|
||||||
|
|
||||||
namespace pushbox {
|
namespace pushbox {
|
||||||
|
|
||||||
class StartScene : public extra2d::Scene {
|
/**
|
||||||
|
* @brief Push Box 开始场景(主菜单)
|
||||||
|
*/
|
||||||
|
class StartScene : public BaseScene {
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 构造函数
|
||||||
|
*/
|
||||||
StartScene();
|
StartScene();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 场景进入时调用
|
||||||
|
*/
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 每帧更新
|
||||||
|
* @param dt 帧间隔时间
|
||||||
|
*/
|
||||||
void onUpdate(float dt) override;
|
void onUpdate(float dt) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
/**
|
||||||
|
* @brief 更新菜单颜色
|
||||||
|
*/
|
||||||
void updateMenuColors();
|
void updateMenuColors();
|
||||||
void updateSoundIcon();
|
|
||||||
|
/**
|
||||||
|
* @brief 执行选中的菜单项
|
||||||
|
*/
|
||||||
void executeMenuItem();
|
void executeMenuItem();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 开始新游戏
|
||||||
|
*/
|
||||||
void startNewGame();
|
void startNewGame();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 继续游戏
|
||||||
|
*/
|
||||||
void continueGame();
|
void continueGame();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 退出游戏
|
||||||
|
*/
|
||||||
void exitGame();
|
void exitGame();
|
||||||
|
|
||||||
extra2d::Ptr<extra2d::FontAtlas> font_;
|
extra2d::Ptr<extra2d::FontAtlas> font_;
|
||||||
extra2d::Ptr<extra2d::Button> startBtn_;
|
extra2d::Ptr<extra2d::Button> startBtn_;
|
||||||
extra2d::Ptr<extra2d::Button> resumeBtn_;
|
extra2d::Ptr<extra2d::Button> resumeBtn_;
|
||||||
extra2d::Ptr<extra2d::Button> exitBtn_;
|
extra2d::Ptr<extra2d::Button> exitBtn_;
|
||||||
extra2d::Ptr<extra2d::Sprite> soundIcon_;
|
extra2d::Ptr<extra2d::Button> soundBtn_;
|
||||||
int selectedIndex_ = 0;
|
int selectedIndex_ = 0;
|
||||||
int menuCount_ = 3;
|
int menuCount_ = 3;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,20 @@
|
||||||
|
// ============================================================================
|
||||||
|
// SuccessScene.cpp - Push Box 通关场景实现
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
#include "SuccessScene.h"
|
#include "SuccessScene.h"
|
||||||
|
|
||||||
#include <extra2d/extra2d.h>
|
#include <extra2d/extra2d.h>
|
||||||
|
|
||||||
namespace pushbox {
|
namespace pushbox {
|
||||||
|
|
||||||
SuccessScene::SuccessScene() {
|
SuccessScene::SuccessScene() : BaseScene() {
|
||||||
auto& app = extra2d::Application::instance();
|
// BaseScene 已处理视口设置
|
||||||
auto& config = app.getConfig();
|
|
||||||
setViewportSize(static_cast<float>(config.width), static_cast<float>(config.height));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 加载菜单字体
|
||||||
|
*/
|
||||||
static extra2d::Ptr<extra2d::FontAtlas> loadMenuFont() {
|
static extra2d::Ptr<extra2d::FontAtlas> loadMenuFont() {
|
||||||
auto& resources = extra2d::Application::instance().resources();
|
auto& resources = extra2d::Application::instance().resources();
|
||||||
auto font = resources.loadFont("assets/font.ttf", 28);
|
auto font = resources.loadFont("assets/font.ttf", 28);
|
||||||
|
|
@ -17,16 +22,15 @@ static extra2d::Ptr<extra2d::FontAtlas> loadMenuFont() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SuccessScene::onEnter() {
|
void SuccessScene::onEnter() {
|
||||||
Scene::onEnter();
|
BaseScene::onEnter();
|
||||||
|
|
||||||
auto& app = extra2d::Application::instance();
|
auto& app = extra2d::Application::instance();
|
||||||
auto& resources = app.resources();
|
auto& resources = app.resources();
|
||||||
setBackgroundColor(extra2d::Colors::Black);
|
|
||||||
|
|
||||||
if (getChildren().empty()) {
|
if (getChildren().empty()) {
|
||||||
// 获取窗口尺寸
|
// 使用游戏逻辑分辨率
|
||||||
float screenW = static_cast<float>(app.getConfig().width);
|
float screenW = GAME_WIDTH;
|
||||||
float screenH = static_cast<float>(app.getConfig().height);
|
float screenH = GAME_HEIGHT;
|
||||||
|
|
||||||
auto bgTex = resources.loadTexture("assets/images/success.jpg");
|
auto bgTex = resources.loadTexture("assets/images/success.jpg");
|
||||||
if (bgTex) {
|
if (bgTex) {
|
||||||
|
|
@ -61,7 +65,7 @@ void SuccessScene::onEnter() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SuccessScene::onUpdate(float dt) {
|
void SuccessScene::onUpdate(float dt) {
|
||||||
Scene::onUpdate(dt);
|
BaseScene::onUpdate(dt);
|
||||||
|
|
||||||
auto& app = extra2d::Application::instance();
|
auto& app = extra2d::Application::instance();
|
||||||
auto& input = app.input();
|
auto& input = app.input();
|
||||||
|
|
@ -69,8 +73,8 @@ void SuccessScene::onUpdate(float dt) {
|
||||||
// A键确认返回主菜单
|
// A键确认返回主菜单
|
||||||
if (input.isButtonPressed(extra2d::GamepadButton::A)) {
|
if (input.isButtonPressed(extra2d::GamepadButton::A)) {
|
||||||
auto& scenes = extra2d::Application::instance().scenes();
|
auto& scenes = extra2d::Application::instance().scenes();
|
||||||
scenes.popScene(extra2d::TransitionType::Fade, 0.2f);
|
scenes.popScene(extra2d::TransitionType::Fade, 0.5f);
|
||||||
scenes.popScene(extra2d::TransitionType::Fade, 0.2f);
|
scenes.popScene(extra2d::TransitionType::Fade, 0.5f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,33 @@
|
||||||
|
// ============================================================================
|
||||||
|
// SuccessScene.h - Push Box 通关场景
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "BaseScene.h"
|
||||||
#include <extra2d/extra2d.h>
|
#include <extra2d/extra2d.h>
|
||||||
|
|
||||||
namespace pushbox {
|
namespace pushbox {
|
||||||
|
|
||||||
class SuccessScene : public extra2d::Scene {
|
/**
|
||||||
|
* @brief Push Box 通关场景
|
||||||
|
*/
|
||||||
|
class SuccessScene : public BaseScene {
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 构造函数
|
||||||
|
*/
|
||||||
SuccessScene();
|
SuccessScene();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 场景进入时调用
|
||||||
|
*/
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 每帧更新
|
||||||
|
* @param dt 帧间隔时间
|
||||||
|
*/
|
||||||
void onUpdate(float dt) override;
|
void onUpdate(float dt) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue