SDL游戏教程第一课 课程基础

时间:2022-02-02 20:10:36
翻译声明:
    本系列教程来自
Dev Hub,一切解释权归原作者。我只是出自个人爱好,才翻译了本系列教程。因为本人也是个初学者,而且英语水平有限,错误难免,望各路高手指正。

本课原文地址:http://www.sdltutorials.com/sdl-tutorial-basics/

这些课程面向那些有一定C++经验,或是其他编程语言的人。如果你跟不上代码本身,而不是概念性问题(游戏相关的),那我建议你还是先读读我们关于解释C++编程语言的课程。那虽然不是掌握全部C++所必须的,但却对以后会有点帮助。

在本系列教程里,我们将使用CodeBlocks作为我们的IDE,gcc和mingw作为编译器。如果你想用其他IDE和编译器,那随便你,但如果你对连接库没什么经验,或许不是很好搞。如果你要下载CodeBlocks,你可以免费从此获得http://www.codeblocks.org (下载那个包含mingw包的)。强烈建议你使用稳定版,除非你想花费更多的时间和夜以继日的修改。

本系列教程会紧密围绕SDL(Simple DirectMedia Layer),一个2D跨平台图形库。此库可以让我们在屏幕上绘制梦幻般的图像,和所有有趣的东西来创建我们的游戏。你需要到http://www.libsdl.org下载这个库,一定要下载“开发库”下的Mingw32的tar文件和“运行库”下的“Win32”的压缩包。如果你在用Visual Studio,可以下载相应的版本替代Minw32文件。一旦下载完成,建议你把压缩包里的.dll问及爱你放到你的system32目录下。这是运行一个SDL应用程序所需要的。

现在打开tar文件(就是在“开发库”下面列出的那个),然后解压到一个目录(比如,C:/SDL)。现在,打开CodeBlocks,需要改变一些设置。点击菜单栏上的“设置”,然后点击“查找目录”选项页。你需要添加C:/SDL/include,到“编译器”选项页,并把C:/SDL/lib添加到“连接器”选项页(把C:/SDL改成你解压文件所在的那个目录)。完成以后,点击Okay。

开始一个新的“空白”工程,随便你叫它什么。保存到某个地方。点击“工程”,然后点击“属性”会弹出一个对话框;在右下角点击“工程建立选项...”按钮。点击“连接器设置”,添加如下内容到“链接库”下的列表:

mingw32
SDLmain
SDL

顺序很重要,一定要保持上面这个次序。如果你搞不明白我们现在做什么,我们仅仅是在把代码“连接”在一块儿,或者,换句话说,我们在把SDL代码融入自己的代码中。通过使用include文件进行编译,lib文件进行连接。一旦完成,你的代码就会组成一个整体以生成一个应用程序。

点击两次OK,然后就完成了所有设置!

让我们创建两个新文件CApp.h和CApp.cpp;他们将担任我们程序的核心部分。首先,打开CApp.h,然后添加如下代码,从此,我们的课程才真正开始:

现在,打开CApp.h,添加如下代码:
  1. #include "CApp.h"
  2. CApp::CApp() {
  3. }
  4. int CApp::OnExecute() {
  5.     return 0;
  6. }
  7. int main(int argc, char* argv[]) {
  8.     CApp theApp;
  9.     return theApp.OnExecute();
  10. }
这个CApp类为我们的整个程序做好基础。我们先到一边看看一个游戏如何进行典型的设置。几乎所有的游戏都是由5个处理游戏过程的函数组成的。这些过程差不多都是这样:

初始化
处理所有的数据加载,可以是纹理贴图、地图、或NPC什么的。

消息
处理所有的输入消息,来自鼠标、键盘、游戏杆、或其他什么设备。

循环
处理所有的数据更新,比如,NPC的移动、减少你的血条、或其他什么。

渲染
处理所有的场景渲染,它并不操作数据,因为那应该是由循环函数完成的。

清理
简单的清理掉所有加载的资源,然后声明一个游戏的正确退出。

了解游戏就是一个巨大的循环,这点很重要。在这个循环内我们找到消息,更新数据,然后渲染图片。所以,最基本的结构差不多如下:

Initialize();

while(true) {
    Events();
    Loop();
    Render();
}

Cleanup();


每个循环间隔,我们修改一些数据,然后进行相应的渲染。消息是附加的,只不过是用户操作数据的一种方式。在某种意义上,消息并不是制作一个游戏所必须的,但是当我们希望用户去操作数据的时候(比如向左移动一个NPC)却需要它。

举个例子就会更清楚了。比如我们有一个骑士,这个游戏的英雄。我们想做的仅仅是让他兜圈子。如果我按下左,他就向左走。我们需要在一个循环中解决如何实现它。首先,我们知道要检查消息(键盘消息)。因为消息是操作数据的一种手段,我们还知道还需要一些某种类型的变量要修改。于是,我们可以使用这些变量在屏幕上合适的位置来渲染我们的骑士。我们需要:

if(Key == LEFT) X–;
if(Key == RIGHT) X++;
if(Key == UP) Y–;
if(Key == DOWN) Y++;//… somewhere else in our code …

RenderImage(KnightImage, X, Y);


它能运行,是因为每次循环都检查key是LEFT,RIGHT,等,若如此,我们增加或者减小一个变量。所以,如果我们的游戏以30帧每秒的速度运行并且我们按下LEFT,那么这家伙每秒就会向左移动30个像素。如果你不明白游戏循环到底是怎么回事,待会你就明白了。游戏需要他们来正确运行。

回到我们的概念代码(那5个函数),我们可以添加一些附加文件到我们的工程:

CApp_OnInit.cpp
CApp_OnEvent.cpp
CApp_OnLoop.cpp
CApp_OnRender.cpp
CApp_OnCleanup.cpp

回到CApp.h,然后添加如下函数和变量:
  1. #ifndef _CAPP_H_
  2. #define _CAPP_H_
  3. #include <SDL.h>
  4. class CApp {
  5.     private:
  6.         bool    Running;
  7.     public:
  8.         CApp();
  9.         int OnExecute();
  10.     public:
  11.         bool OnInit();
  12.         void OnEvent(SDL_Event* Event);
  13.         void OnLoop();
  14.         void OnRender();
  15.         void OnCleanup();
  16. };
  17. #endif
创建他们自己的函数,完成刚创建的每一个文件:
  1. #include "CApp.h"
  2. bool CApp::OnInit() {
  3.     return true;
  4. }
  5. #include "CApp.h"
  6. void CApp::OnEvent(SDL_Event* Event) {
  7. }
  8. #include "CApp.h"
  9. void CApp::OnLoop() {
  10. }
  11. #include "CApp.h"
  12. void CApp::OnRender() {
  13. }
  14. #include "CApp.h"
  15. void CApp::OnCleanup() {
  16. }
现在,让我们回到我们的CApp.cpp代码把所有这些函数链接在一起:
  1. #include "CApp.h"
  2. CApp::CApp() {
  3.     Running = true;
  4. }
  5. int CApp::OnExecute() {
  6.     if(OnInit() == false) {
  7.         return -1;
  8.     }
  9.     SDL_Event Event;
  10.     while(Running) {
  11.         while(SDL_PollEvent(&Event)) {
  12.             OnEvent(&Event);
  13.         }
  14.         OnLoop();
  15.         OnRender();
  16.     }
  17.     OnCleanup();
  18.     return 0;
  19. }
  20. int main(int argc, char* argv[]) {
  21.     CApp theApp;
  22.     return theApp.OnExecute();
  23. }

你会发现一些新的变量,然而,让我们看看先有什么事发生。首先,我们试着初始化我们的游戏,若失败我们返回-1(一个错误代码),于是,关闭游戏。如果一切正常,那就继续我们的游戏循环。在游戏循环里,我们使用SDL_PollEvent()来检查消息,并把他们逐一传递给OnEvent()。一旦完成消息,我们到OnLoop()来操作数据,然后渲染我们的游戏。我们无休止地重复它。若用户退出游戏,我们到OnCleanup()来清理所有的资源。就这么简单。

现在,让我们看看SDL_Event和SDL_PollEvent()。第一个是一个包含了消息信息的结构体。第二个是一个抓取任意在队列里等待消息的函数。这个队列里有很多消息,这就是为什么我们要循环遍历他们。那么,举个例子,假如用户在OnRender()函数执行期间,按下A并且移动了鼠标。SDL就会检测到它,然后放置两个消息到队列中去,一个是按键的,一个是鼠标移动的。我们可以通过SDL_PollEvent()从队列里抓取一个消息,然后把它传递给OnEvent()来做相应的处理。一旦队列里没任何消息了,SDL_PollEvent()就会返回false,然后退出消息队列循环。

另一个增加的变量,Running,是私有的。表示我们退出了游戏循环与否。如果它被设置为false,程序就会结束,然后退出程序。那么,举个例子,如果用户按下了Escape键,我们就把它设置为false,退出游戏。

目前,你的编译应该一切顺利,但是,你可能注意到你还是不能退出。你或许还得到任务管理器那里去终止掉这个程序。

现在一切搞定,我们就开始创建一个绘制游戏的窗口。
跳到CApp.h,然后增加一个SDL表面变量到代码:
  1. #ifndef _CAPP_H_
  2. #define _CAPP_H_
  3. #include <SDL.h>
  4. class CApp {
  5.     private:
  6.         bool            Running;
  7.         SDL_Surface*    Surf_Display;
  8.     public:
  9.         CApp();
  10.         int OnExecute();
  11.     public:
  12.         bool OnInit();
  13.         void OnEvent(SDL_Event* Event);
  14.         void OnLoop();
  15.         void OnRender();
  16.         void OnCleanup();
  17. };
  18. #endif

我感觉现在是时候后好好解释一下什么是一个SDL表面了。一个SDL表面是任何你可以在其上进行绘制的东西。假如我们有一张白纸、一支铅笔、和一些贴纸;这张纸就可以被称作是我们的显示“表面”,我们可以在它上面画东西,把贴纸放上面或别的什么东西。我们的贴纸也是表面;我们可以在贴纸上绘画,然后把它贴到别的贴纸上去。那么,Surf_Display就是我们的“一张白纸”。我们要在它上面绘制我们所有的东西。

现在,让我们回到CApp_OnInit来真正的创建这张表面:
  1. #include "CApp.h"
  2. bool CApp::OnInit() {
  3.     if(SDL_Init(SDL_INIT_EVERYTHING) < 0) {
  4.         return false;
  5.     }
  6.     if((Surf_Display = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF)) == NULL) {
  7.         return false;
  8.     }
  9.     return true;
  10. }

首先要做的一件事就是启动SDL本身,然后我们才能访问它的函数。我们告诉SDL去初始化它所有的一切;有其他参数你可以传递,但是现在搞明白他们并不是很重要。下一个函数就是SDL_SetVideoMode()。他就是创建我们的窗口和表面的那个家伙。他有4个参数:窗口宽度,窗口高度,窗口位深度(建议16或32),然后是显示标志。显示标志相当多,但是上面显示的那个现在用就正好。第一个标志位告诉SDL使用硬件内存来存储我们的图片等,第二个标志位告诉SDL使用双缓存(如果你不想闪屏,这个很重要)。还有一个标志位,或许你会感兴趣,那就是SDL_FULLSCREEN,它可以使你的窗口全屏化显示。

现在我们的显示也搞定了,然我们稍微做些清理,以保证一切工作顺畅。打开CApp_OnCleanup.cpp,然后添加如下:
  1. #include "CApp.h"
  2. void CApp::OnCleanup() {
  3.     SDL_FreeSurface(Surf_Display);
  4.     SDL_Quit();
  5. }
我们要做的第一件事,就是释放掉这个显示表面,基本就是释放一些内存空间。之后,我们就退出SDL。你要注意下,这里也是你释放其他表面的地方(或许在Surf_Display之前)。这保证了你所有的代码都集中到它的功能实现上。

为了保持整洁,我们也把Surf_Display指针在构造函数里设置为NULL。
打开CApp.cpp,添加如下:
  1. CApp::CApp() {
  2.     Surf_Display = NULL;
  3.     Running = true;
  4. }
试试编译你的代码,看看能不能运行。你会得到一个弹出的空窗口。
你会发现,你还是不能关掉它,还得去“劳驾“任务管理器。

现在,我们已经搞定一个窗口了,我们要做的就是找一种方式去关闭它。
打开CApp_OnEvent.cpp文件,添加如下:
  1. #include "CApp.h"
  2. void CApp::OnEvent(SDL_Event* Event) {
  3.     if(Event->type == SDL_QUIT) {
  4.         Running = false;
  5.     }
  6. }
这个SDL消息结构体被肢解到types了。这些types可以包括按键、鼠标移动;我们要做得仅仅是在此检查消息类型。上面我们找到的那个类型是关闭窗口需要的(比如,当用户点击X按钮)。如果那种消息发生,我们就设置Running为false,然后终止程序。就这么简单。我们会在后续课程当中再仔细琢磨消息。

现在,一切搞定了,并且是一个日后可以再用的优秀结构。或许把此工程改造成一个CodeBlock里的”SDL template“是个不错的想法。我不想回头去做了,只是随意Google一下。

如果你想知道我们在此提到的代码发生了其他什么事,跳到下一节课去学更多的SDL表面吧。

SDL 课程基础 —— 教程文件:
Win32: Zip, Rar
Linux: Tar(感谢Gaten)