本系列教程来自 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,添加如下代码:
- #include "CApp.h"
- CApp::CApp() {
- }
- int CApp::OnExecute() {
- return 0;
- }
- int main(int argc, char* argv[]) {
- CApp theApp;
- return theApp.OnExecute();
- }
初始化
处理所有的数据加载,可以是纹理贴图、地图、或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,然后添加如下函数和变量:
- #ifndef _CAPP_H_
- #define _CAPP_H_
- #include <SDL.h>
- class CApp {
- private:
- bool Running;
- public:
- CApp();
- int OnExecute();
- public:
- bool OnInit();
- void OnEvent(SDL_Event* Event);
- void OnLoop();
- void OnRender();
- void OnCleanup();
- };
- #endif
- #include "CApp.h"
- bool CApp::OnInit() {
- return true;
- }
- #include "CApp.h"
- void CApp::OnEvent(SDL_Event* Event) {
- }
- #include "CApp.h"
- void CApp::OnLoop() {
- }
- #include "CApp.h"
- void CApp::OnRender() {
- }
- #include "CApp.h"
- void CApp::OnCleanup() {
- }
- #include "CApp.h"
- CApp::CApp() {
- Running = true;
- }
- int CApp::OnExecute() {
- if(OnInit() == false) {
- return -1;
- }
- SDL_Event Event;
- while(Running) {
- while(SDL_PollEvent(&Event)) {
- OnEvent(&Event);
- }
- OnLoop();
- OnRender();
- }
- OnCleanup();
- return 0;
- }
- int main(int argc, char* argv[]) {
- CApp theApp;
- return theApp.OnExecute();
- }
你会发现一些新的变量,然而,让我们看看先有什么事发生。首先,我们试着初始化我们的游戏,若失败我们返回-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表面变量到代码:
- #ifndef _CAPP_H_
- #define _CAPP_H_
- #include <SDL.h>
- class CApp {
- private:
- bool Running;
- SDL_Surface* Surf_Display;
- public:
- CApp();
- int OnExecute();
- public:
- bool OnInit();
- void OnEvent(SDL_Event* Event);
- void OnLoop();
- void OnRender();
- void OnCleanup();
- };
- #endif
我感觉现在是时候后好好解释一下什么是一个SDL表面了。一个SDL表面是任何你可以在其上进行绘制的东西。假如我们有一张白纸、一支铅笔、和一些贴纸;这张纸就可以被称作是我们的显示“表面”,我们可以在它上面画东西,把贴纸放上面或别的什么东西。我们的贴纸也是表面;我们可以在贴纸上绘画,然后把它贴到别的贴纸上去。那么,Surf_Display就是我们的“一张白纸”。我们要在它上面绘制我们所有的东西。
现在,让我们回到CApp_OnInit来真正的创建这张表面:
- #include "CApp.h"
- bool CApp::OnInit() {
- if(SDL_Init(SDL_INIT_EVERYTHING) < 0) {
- return false;
- }
- if((Surf_Display = SDL_SetVideoMode(640, 480, 32, SDL_HWSURFACE | SDL_DOUBLEBUF)) == NULL) {
- return false;
- }
- return true;
- }
首先要做的一件事就是启动SDL本身,然后我们才能访问它的函数。我们告诉SDL去初始化它所有的一切;有其他参数你可以传递,但是现在搞明白他们并不是很重要。下一个函数就是SDL_SetVideoMode()。他就是创建我们的窗口和表面的那个家伙。他有4个参数:窗口宽度,窗口高度,窗口位深度(建议16或32),然后是显示标志。显示标志相当多,但是上面显示的那个现在用就正好。第一个标志位告诉SDL使用硬件内存来存储我们的图片等,第二个标志位告诉SDL使用双缓存(如果你不想闪屏,这个很重要)。还有一个标志位,或许你会感兴趣,那就是SDL_FULLSCREEN,它可以使你的窗口全屏化显示。
现在我们的显示也搞定了,然我们稍微做些清理,以保证一切工作顺畅。打开CApp_OnCleanup.cpp,然后添加如下:
- #include "CApp.h"
- void CApp::OnCleanup() {
- SDL_FreeSurface(Surf_Display);
- SDL_Quit();
- }
为了保持整洁,我们也把Surf_Display指针在构造函数里设置为NULL。
打开CApp.cpp,添加如下:
- CApp::CApp() {
- Surf_Display = NULL;
- Running = true;
- }
你会发现,你还是不能关掉它,还得去“劳驾“任务管理器。
现在,我们已经搞定一个窗口了,我们要做的就是找一种方式去关闭它。
打开CApp_OnEvent.cpp文件,添加如下:
- #include "CApp.h"
- void CApp::OnEvent(SDL_Event* Event) {
- if(Event->type == SDL_QUIT) {
- Running = false;
- }
- }
现在,一切搞定了,并且是一个日后可以再用的优秀结构。或许把此工程改造成一个CodeBlock里的”SDL template“是个不错的想法。我不想回头去做了,只是随意Google一下。
如果你想知道我们在此提到的代码发生了其他什么事,跳到下一节课去学更多的SDL表面吧。
SDL 课程基础 —— 教程文件:
Win32: Zip, Rar
Linux: Tar(感谢Gaten)