由于历史原因,OSX 没能像 Windows 一样成为游戏玩家的首选。这也导致了大部分的游戏开发的书籍关注的是 Visual C++ 上关于 Windows 的游戏编程。由于我日常用的是 Macbook Pro,最近也想复习一下 C++ 编程,想干一些有趣的事情。
在 OSX 进行 C++ 开发,首选的 IDE 自然是 Apple 自家的 Xcode。为了创建游戏窗体,使用的是一个跨平台的第三方库,Simple DirectMedia Layer(SDL),它负责处理创建窗体、2D 图形、音频输出、键盘输入等过程。
我们直接使用 brew install SDL2
命令,就可以一键式的安装好 SDL。
brew 安装的默认路径(似乎是)
/usr/local/Cellar
好了,这样一来,我们所需游戏环境就搭好了。
游戏需要在运行时每秒钟进行多次刷新,这可能是游戏和其他程序最大的区别了。如果游戏以每秒60帧(FPS)运行,这就意味着游戏循环每秒完成60次迭代。游戏每秒运行这么多次的迭代,就造成了连续运动的错觉,实际上它只是定时在刷新。
游戏的每次循环迭代,都可以视为一个框架,基本上可以在宏观上划分成三个过程:
有了上述的简单介绍,就可以开始开发一个基本的游戏骨架了。我们需要引入 SDL 库的头文件。我们新建一个 Game.cpp
,Xcode 会默认生成 Game.hpp
。
我们在 Game.hpp
中导入 SDL 的头文件:
:
#ifndef Game_hpp
#define Game_hpp
#include
#endif /* Game_hpp */
要利用 SDL 创建一个窗体程序,我们需要引用到 SDL_Window
的指针,作为一个游戏基本过程,可以分成初始化,运行时的游戏循环,关闭游戏这三个阶段。简而言之,创建、运行和关闭。
进一步地,根据上面对游戏循环过程的简单介绍。我们可以将游戏循环的过程划分成三个大致步骤:ProcessInput
、UpdateGame
、GenerateOutput
。为了判断游戏是否应该继续运行,不妨加上一个 bool
变量 mIsRunning
。
C++ 作为一个支持面向对象的语言,我们可以创建一个 Game
类来完成上述设计。设计和实现可以分离出来,我们先在 Game.hpp
头文件中声明 Game
类。
// Game class
class Game
{
public:
Game();
// 初始化游戏
bool Initialize();
// 运行游戏循环直到游戏结束
void RunLoop();
// 关闭游戏
void Shutdown();
private:
// 处理进程输入
void ProcessInput();
// 更新游戏
void UpdateGame();
// 生成输出
void GenerateOutput();
// 通过 SDL 创建窗体
SDL_Window* mWindow;
// 继续运行
bool mIsRunning;
};
设计阶段就这样结束了,我们可以在 Game.cpp
中开始编写具体实现了。
Game()
作为一个简单的构造函数,只需要把 mWindow
初始化为 nullptr
,mIsRunning
初始化为 true
。
Game::Game()
:mWindow(nullptr)
,mIsRunning(true)
{
}
Initialize
函数如果返回 true
则代表初始化成功,false
则代表创建失败。初始化时,需要先初始化 SDL 库,需要调用 SDL 库提供的 SDL_Init
函数:
// 初始化 SDL 库
int sdlResult = SDL_Init(SDL_INIT_VIDEO);
if (sdlResult != 0)
{
SDL_Log("不能初始化 SDL: %s", SDL_GetError());
return false;
}
SDL_Init
接受一个 UInt32
的标志位子系统,需要多个子系统,只需要使用 |
(OR)运算联系起来,具体的子系统列表见API 文档。这是初始化的是视频子系统。SDL_Init
初始化成功时返回 0 ,失败时返回负错误代码。出错时可以调用 SDL_GetError()
来获取更多的信息。SDL_Log
和 printf
类似,把信息输出到控制台。
如果 SDL 库初始化成功,接下来就是用 SDL_CreateWindow
函数来创建窗体。
// 创建 SDL 窗体
mWindow = SDL_CreateWindow(
"游戏环境的搭建", // 标题
100, // 窗体左上角的 x 坐标
100, // 窗体左上角的 y 坐标
1024, // 窗体宽度
768, // 窗体高度
0 // 标志位
);
if (!mWindow)
{
SDL_Log("创建窗体失败: %s", SDL_GetError());
return false;
}
最后的标志位可以用来控制窗体是否全屏,例如将 0
改成预置常量:SDL_WINDOW_FULLSCREEN
,可以获得全屏的效果。窗体初始化的时候返回的是 SDL_Window*
,因此创建失败时将是 nullptr
,检查的手法和 SDL_Init
是一样的。
到了这里,初始化 SDL 和窗体都是成功的话,那么可以返回 true
。
return true;
Shutdown
和 Initialize
正好相反。由于窗体是指针类型,我们需要手动销毁:
void Game::Shutdown()
{
SDL_DestroyWindow(mWindow);
SDL_Quit();
}
游戏循环一直迭代,直到 mIsRunning
变成 false
。根据前面所说,游戏循环的过程划分成三个部分:
void Game::RunLoop()
{
while (mIsRunning)
{
ProcessInput();
UpdateGame();
GenerateOutput();
}
}
在任何一个桌面操作系统中,用户都可以在窗体上执行某些操作,例如移动窗体、最小化或最大化窗体(P.S. 创建窗体的时候可以通过标志位设置),还有像关闭窗体等等。这些不同的选择用不同的事件(events)表示。用户执行不同的操作时,程序从操作系统接收到事件,并根据事件作出不同的响应。
事件可能不止一个,SDL 维护了一个内部队列来接收保存操作系统传入的事件。当然,并不是我们需要处理所有的事件,对于用户一些无理请求,我们可以选择直接忽略。如果这些输入的代码将实现在 ProcessInput()
中。
SDL_PollEvent
将在访问 SDL 内部事件队列,如果队列存在事件,将返回 true
。因此,一个基本的实现就是一直调用 SDL_PollEvent
,只要它返回 true
:
void Game::ProcessInput() {
SDL_Event event;
// 有 event 在队列就一直循环
while (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_QUIT:
mIsRunning = false;
break;
default:
break;
}
}
}
SDL 内部队列的元素类型是 SDL_Event
,由于函数需要修改 event
,所以需要传递元素指针(SDL 是用 C语言 编写的,因此叫指针,不叫引用了)。SDL_Event
的类型实际上是 union
,在这里是 Uint32
的类型。
在这里,我们处理了 SDL_QUIT
类型的事件,现在可以实现关闭窗口了。好了,先运行一下,看看效果。
要运行程序,main
作为入口是必不可少的。实现起来也很简单,初始化,然后进入游戏循环,退出游戏循环后,关闭游戏。
#include "Game.hpp"
int main(int argc, const char * argv[]) {
Game game;
bool success = game.Initialize();
if (success)
{
game.RunLoop();
}
game.Shutdown();
return 0;
}
编译运行(另外两个函数先定义,但留空就行),效果是这样的:
现在点击左上角的叉,可以关闭这个窗口了。
用户如果希望按 ESC 键退出窗体,实现的原理也是一样的。键盘的输入同样是事件,要获取键盘的状态用 SDL_GetKeyboardState
,它将返回一个包含当前键盘状态的数组指针。可以通过检查 SDL_SCANCODE
的值来获取该键的状态,ESC 键就是 SDL_SCANCODE_ESCAPE
。
void Game::ProcessInput() {
SDL_Event event;
// 有 event 在队列就一直循环
while (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_QUIT:
mIsRunning = false;
break;
default:
break;
}
}
// 获取键盘的状态
const Uint8* state = SDL_GetKeyboardState(NULL);
// 如果按了 Esc,结束循环
if (state[SDL_SCANCODE_ESCAPE])
{
mIsRunning = false;
}
}
可以编译一下试试。至于后面两个函数,就暂时先搁置一下,留空处理。在编写这两个函数之前,先了解一下 2D 图形如何在游戏上工作。好了,下次继续。
下一篇:Xcode与C++之游戏开发:2D图形