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_enter和on_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,就可以直接在这里评论。