跳转至

01-场景设计和头文件规则怪谈

1 场景

如果把游戏比作演出,则每个场景就是这个演出中的一幕,在不同的幕中,有不同的“剧本”逻辑,也会有不同的角色登场。这些角色就是GameObject,比如玩家、敌人、子弹、道具等。
游戏的主菜单、选择角色的界面、以及游戏开始后都可以看作是不同的场景。
所以我们可以定义Scene这个场景基类。主菜单、角色选择、局内游戏这三个场景分别继承自Scene这个基类。

2 基础程序

2.1 main()函数

首先复习一下前面提到的游戏主循环框架的概念。游戏程序的主体是一个永不停歇的死循环,在每次循环中,我们读取玩家操作,并根据这些操作处理玩家数据更新,在最后的绘图阶段,将游戏画面根据这些更新后的数据渲染出来,

初始化();
while(true)
{
    读取操作();
    处理数据();
    绘制画面();
}
释放资源();

也就是这样的代码。
#include <graphics.h>

int main()
{
    ExMessage msg;
    const int FPS = 60;

    initgraph(1280, 720);

    BeginBatchDraw();

    while(true)
    {
        DWORD frame_start_time = GetTickCount();

        while(peekmessage(&msg)) 
        {

        }

        cleardevice();

        FlushBatchDraw();

        DWORD frame_end_time = GetTickCount();
        DWORD frame_delta_time = frame_end_time - frame_start_time;

        if (frame_delta_time < 100/FPS) {
            Sleep(1000 / FPS - frame_delta_time);
        }

    }
    EndBatchDraw();

    return 0;
}

2.2 CMake配置

cmake_minimum_required(VERSION 3.10)
project(plant_battle)

set(CMAKE_CXX_STANDARD 17)

add_compile_options(-finput-charset=UTF-8 -fexec-charset=GBK)

add_executable(plant_battle main.cpp )

# 设置EasyX路径
set(EasyX_INCLUDE_DIR "C:/msys64/mingw64/include")
set(EasyX_LIB_DIR "C:/msys64/mingw64/lib")

set(EasyX_LIB_PATH "${EasyX_LIB_DIR}/libeasyx.a")

# 包含头文件目录
target_include_directories(plant_battle PRIVATE ${EasyX_INCLUDE_DIR})

# 链接EasyX库
target_link_libraries(plant_battle PRIVATE ${EasyX_LIB_PATH})

3 Scene类

新建一个scene.h头文件
预处理指令

#pragma once

可以让这个头文件中的内容不会被重复包含。下面这种方法也可以做到相同的效果
#ifndef _SCENE_H_
#define _SCENE_H_

#endif // !_SCENE_H_

我们设计了几个成员方法,对应我们游戏主循环的各个阶段,

  • 常用于在处理玩家输入时调用 on_input 方法
  • 在处理数据时调用 on_update 方法,
  • 在渲染绘图时调用 on_draw 方法,
  • 我们还提供了 on_enteron_exit 两个成员方法,来处理场景进入时的初始化逻辑和场景退出时的卸载逻辑。
    我们把这些成员方法都声明为了虚函数,这意味着具体的游戏场景类只需要继承自 Scene 这个基类后就可以通过重写自己的对应方法,借助面向对象的多态特性实现自己的逻辑。而 Scene 基类在这个过程中更像是其他具体用于实例化的场景子类的模板。
    class Scene
    {
    public:
        Scene() = default;
        ~Scene() = default;
    
        virtual void on_enter() { }
        virtual void on_update() { }
        virtual void on_draw() { }
        virtual void on_input(const ExMessage& msg) { }
        virtual void on_exit() { }
    
    private:
    
    };
    

3.1 子类MenuScene

class MenuScene : public Scene
{
public:
    MenuScene() = default;
    ~MenuScene() = default;
private:

};

之后我们重写各个on_xxx()方法
void on_enter() {
    std::cout << "进入主菜单" << std::endl;
}

void on_update() { 
    std::cout << "主菜单正在运行" << std::endl;
}

void on_draw() {
    outtextxy(10, 10, _T("主菜单绘图内容"));
}

void on_input(const ExMessage& msg) { }

void on_exit() { 
    std::cout << "主菜单退出" << std::endl;
}

在CMakeLists.txt中添加头文件目录

target_include_directories(plant_battle PRIVATE ${PROJECT_SOURCE_DIR}/include)

在main函数中定义MenuScene对象,并在主循环内部的对应阶段调用他的方法

Scene *scene = new MenuScene();
scene->on_enter();
...
scene->on_input(msg);
...
scene->on_update();
...
scene->on_draw();
...

EasyX默认会隐藏控制台,所以在initgraph时要显式地复制为显示控制台窗口

现在遇到的问题

add_compile_options(-finput-charset=UTF-8 -fexec-charset=GBK)

使得程序输出的字符为GBK编码,而vscode集成终端中用于显示的编码方式为utf8,导致集成终端显示乱码。

3.2 子类GameScene

类似地,实现游戏局内场景GameScene

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

    void on_enter() {
        std::cout << "进入游戏局内场景" << std::endl;
    }

    void on_update() { 
        std::cout << "游戏正在运行..." << std::endl;
    }

    void on_draw() {
        outtextxy(10, 10, _T("游戏局内绘图内容"));
    }

    void on_input(const ExMessage& msg) { }

    void on_exit() { 
        std::cout << "游戏局内退出" << std::endl;
    }

private:

};

评论

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