跳转至

02-场景管理和图集类实现

1 场景管理器

不同场景地切换,需要一个场景管理器进行控制,即SceneManager。
和之前的思路相同,我们把SceneManager在一个新的头文件中定义为类

场景管理器本质上是一个巨大的状态机。
玩家进入游戏的默认状态为主菜单,之后静茹选角界面,再进入游戏局内。游戏结束后再到主菜单状态;

1.1 基本的场景管理器

class SceneManager
{
public:
    enum class SceneType
    {
        Menu,
        Game
    };
public:
    SceneManager() = default;
    ~SceneManager() = default;

    void set_current_scene(Scene* scene)
    {
        current_scene = scene;
        current_scene->on_enter();
    }
private:
    Scene *current_scene = nullptr;
};

之后定义switch_to()方法来实现状态间的切换
void switch_to(SceneType type)
{
    current_scene->on_exit();
    switch(type)
    {
    case SceneType::Menu:
        break;
    case SceneType::Game:
        break;
    default:
        break;
    }
    current_scene->on_enter();
}

这时候可能会有疑问,on_exit()on_enter()方法的作用与构造函数和析构函数类似,为什么还要额外定义呢?
这是因为构造函数和析构函数决定着场景对象在内存中的生命周期。如果我们直接用它们执行进入和退出逻辑。那么我们在场景之间进行跳转时,就要不断地构造新的对象和释放旧的对象,这并不是性能友好的行为,以及随着程序逻辑逐渐复杂,可能会存在有些资源在不同场景之间都有引用

我们回到main.cpp中,定义下面两个全局指针

Scene *menu_scene = nullptr;
Scene *gmae_scene = nullptr;

在初始化阶段,把它们指向对应的场景实例
menu_scene = new MenuScene();
game_scene = new GameScene();

scene_manager.h中获取这两个全局指针的引用
extern Scene *menu_scene;
extern Scene *game_scene;

switch_to()方法内部完善逻辑

void switch_to(SceneType type)
{
    current_scene->on_exit();
    switch(type)
    {
    case SceneType::Menu:
        current_scene = menu_scene;
        break;
    case SceneType::Game:
        current_scene = game_scene;
        break;
    default:
        break;
    }
    current_scene->on_enter();
}

为什么设置当前场景的时候使用的参数是场景指针,而跳转场景的时候使用的是场景枚举呢?

这是因为设置当前场景的方法一般只在游戏初始化时,设置场景管理器的入口场景时才被调用,与子场景实例化几乎同时进行。而切换场景一般是在不同的子场景内部更新时调用,子场景之间持有彼此的引用是一种不太优雅且极容易出现内存问题的行为。所以我们使用枚举来屏蔽掉管理器内部的指针操作。

main.cpp中定义场景管理器

SceneManager scene_manager;

在初始化时指定当前场景
scene_manager.set_current_scene(menu_scene);

SceneManager类中定义下面这几个方法,并在main函数的对应阶段调用它们
void on_update()
{
    current_scene->on_update();
}

void on_draw()
{
    current_scene->on_draw();
}

void on_input(const ExMessage& msg)
{
    current_scene->on_input(msg);
}

1.2 场景之间的跳转

接下来我们编写具体的场景之间进行跳转的代码

我们首先来到在开始的场景MenuScene的头文件中,引入外部变量scene_manager

extern SceneManager scene_manager;

我们希望在主菜单场景中按下任意键,程序都会跳转到游戏场景
void on_input(const ExMessage& msg) { 
    if(msg.message == WM_KEYDOWN)
    {
        scene_manager.switch_to(SceneManager::SceneType::Game);
    }
}

这样我们在主菜单场景按下任意键就能进入游戏场景

同样的,我们在GameScene的头文件中写下相似的代码

void on_input(const ExMessage& msg) { 
    if(msg.message == WM_KEYDOWN)
    {
        scene_manager.switch_to(SceneManager::SceneType::Menu);
    }
}

这样我们不断按下键盘,就能看到游戏在不同场景间跳转的输出了

1.3 玩家角色选择场景

class SelectorScene : public Scene
{
public:
    SelectorScene() = default;
    ~SelectorScene() = default;

    void on_enter() {
    }

    void on_update() { 
    }

    void on_draw() {
    }

    void on_input(const ExMessage& msg) { 
    }

    void on_exit() { 
    }

private:

};

main.cpp中定义对象
Scene *selector_scene = nullptr;
...
selector_scene = new SelectorScene();

SceneManager类中的场景枚举类型加入Selector场景,
enum class SceneType
{
    Menu,
    Game,
    Selector
};

switch_to()方法中添加对应代码
case SceneType::Selector:
    current_scene = selector_scene;
    break;

2 图集类实现

class Atlas
{
public:
    Atlas() = default;
    ~Atlas() = default;
private:
    std::vector<IMAGE> img_list;
};

之后我们定义用于加载帧图片的方法
void load_from_file(LPCTSTR path_template, int num)
{
    img_list.clear();
    img_list.resize(num);

    TCHAR path_file[256];a

    for (int i = 0; i < num; i++) {
        _stprintf(path_file, path_template, i + 1);
        loadimage(&img_list[i], path_file);
    }
}

再定义下面这些常用方法,我们使用get_imag()来获取实际渲染的动画帧
void clear()
{
    img_list.clear();
}

int get_size()
{
    return (int)img_list.size();
}

IMAGE* get_image(int idx)
{
    if (idx < 0 || idx >= img_list.size()) {
        return nullptr;
    }

    return &img_list[idx];
}

void add_image( const IMAGE& img)
{
    img_list.push_back(img);
}

评论

如果你已登录 GitHub,就可以直接在这里评论。