翻译声明:
本系列教程来自 Dev Hub,一切解释权归原作者。我只是出自个人爱好,才翻译了本系列教程。因为本人也是个初学者,而且英语水平有限,错误难免,望各路高手指正。
本课原文地址:http://www.sdltutorials.com/sdl-tutorial-tic-tac-toe/
到了这个时候,我们已经为开发一个游戏打下了良好的基础。至此,我们已经建立了一个最基础的框架来处理一般的规则,而且建立了一个特殊的类来处理消息,还有一个处理一些表面函数的类。本课,我们就要用到全部这些东西,把他们结合起来,然后创建一个井字游戏。别担心,这东西很简单的。在上节课的基础上开始。
第一件要做的事,就是要规划我们的游戏。根据经验,我们知道井字游戏有3*3格子,你可以往这些格子里放置X或O。那么,我们就知道了需要3张图片,一个充当网格,一个充当X,一个充当O。我们并不需要重复的多个X或O,因为可以在程序里多次绘制同一张图片。让我们走出第一步。我们的网格是600*600的,还有X和O都是200*200的。
现在我们已经有图片了,我们需要用一种方法把他们加载到我们的程序里。打开CApp.h做点修改。删除测试用的表面,然后添加3个新表面。
同样的,打开CApp.cpp并做相应修改。删除测试表面,再次添加3个新的。
呵呵,猜对了,打开CApp_OnCleanup.cpp做同样修改。和前面一样,去掉测试表面,然添加3个新的。
现在我们就完成了表面的设定,并把他们加载到了内存。打开CApp_OnInit.cpp,做点修改。在此去掉测试表面,然后加载3个新的。确保文件名都正确。然后,把窗口尺寸改为600*600,也就是网格的大小。这样就可以填满整个窗口而不会在窗口周围用不到的地方留下空白。
或许你已经注意到了我在文件名上做了点修改。我在文件名前添加了./gfx/来指定图片所在的文件夹。在游戏增长之前,一定确定实际上的一个文件夹包含这些文件。正因为此,从此以后,所有的图片将会被放到gfx文件夹。
现在,我们就把网格显示到屏幕上。打开CApp_OnRender.cpp,把测试表面的渲染改成渲染网格。
试试编译下你的程序,如果成功,你应该可以看到网格被显示出来了。一定要记得,使用一个表面一般只要5步:声明、设置为NULL、加载、绘制、然后清理。最好你现在就牢牢地记住这5步,因为假如一会你忘了某一步的话就会有问题了。例如,你忽略了把一个表面设置为NULL,就会导致未定义。如果你忽略了释放一个表面,就会导致内存泄漏。
或许你会感觉我们的图片有点奇怪,怎么X和O都是紫色的背景。当然有原因了,我们要对这些表面实现透明效果。其实,只要是显示紫色的地方我们会把紫色做成透明的。SDL提供了一个简单的函数来完成这个效果,SDL_SetColorKey。为了实现它,打开CSurface.h添加一个新函数。
现在就实现这个函数,打开CSurface.cpp然后添加它:
注意下我们传递的除了表面多出的3个参数。有3个我们要做透明的颜色值,并不仅仅是紫色。举个例子,如果我们要把红色做成透明的,那就是 255,0,0。
这个函数首先检查我们是不是传递了有效的表面。如果是,我们为一个颜色设置一个颜色键(透明)。第一个参数就是我们我应用颜色键的表面,第二个是告诉SDL如何进行操作的一些标志,然后第三个参数是要做透明的颜色。这些用到的标志都很基础,第一个告诉SDL在一个源(就是传递进去的表面)上应用颜色键,第二个告诉SDL试着用RLE加速(其实就是尽量绘制得更快些)。第三个参数有点复杂,为了创建一个颜色,我们用到了SDL_MapRGB。这个函数使用一个表面,还需要指定颜色值(R,G,B),然后试着在表面上去找一个最接近的颜色值来匹配它。你或许在想,这有什么用。并不是所有的表面都有同样的调色板。还记得早期的NES年代里只有很少一些颜色值可以用么?同样的,SDL_MapRGB使用一个颜色值,然后在一个表面的调色板里找一个最接近的值。
现在就让我们对我们的表面应用这个新函数,打开CApp_OnInit.cpp,然后做如下改变:
表面的一切都已搞定。下面我们要做的就是用一种方式来绘制X和O。我们可以在网格的任何地方绘制他们,因为他们并不总是在同一点。下面我们要做的就是创建一个9个容器的数组,由数组里的数据告诉我们网格上每个单元的值。于是0点就是左上,1就是中上,2就是右上,等等。把创建的数组添加到CApp.h:
我们知道,每个单元可取的值包括:空,X和O。它们告诉我们一个单元里目前是什么。为了比0,1,2来得更整洁点,我们在此使用一个enum来代替。如果你不明白enum是怎么回事,可以去找点关于这方面的速成看一下。只要知道GRID_TYPE_NONE = 0,GRID_TYPE_X = 1,GRID_TYPE_O = 2就行了。回到CApp.h,在网格数组下面添加如下:
enum {
GRID_TYPE_NONE = 0,
GRID_TYPE_X,
GRID_TYPE_O
};
注意,目前为止我一旦提到不同文件里的代码都是把它们全都贴出来。从现在开始,我希望你能清除代码进行到哪儿了。多数时候我会告诉你该往哪里添加它,有时我也会把它们全显示出来。
现在我们已经有了实现一个单元的方法,我们就需要一种重置面板的方法。让我们在CApp.h底部创建一个新的函数Reset。
public:
void Reset();
打开CApp.cpp,在main()之前添加如下函数:
void CApp::Reset() {
for(int i = 0;i < 9;i++) {
Grid[i] = GRID_TYPE_NONE;
}
}
这个循环将把网格上所有单元都设置为GRID_TYPE_NONE,意思就是所有单元都是空的。我们要在每次程序加载的最开始这么做,所有在CApp_OnInit.cpp里调用此函数:
//…
CSurface::Transparent(Surf_X, 255, 0, 255);
CSurface::Transparent(Surf_O, 255, 0, 255);
Reset();
目前一切正常。接下来我们就要实现在屏幕上放置X和O了。通过创建一个新函数来搞定它。再次打开CApp.h,就在Reset后面添加:
void SetCell(int ID, int Type);
现在,打开CApp.cpp,添加这个函数:
void CApp::SetCell(int ID, int Type) {
if(ID < 0 || ID >= 9) return;
if(Type < 0 || Type > GRID_TYPE_O) return;
Grid[ID] = Type;
}
此函数有两个参数,第一个是要改变的单元的ID,第二个是要改变成的类型。我们需要在此限制一下,首先要保证访问数组不能越界(否则我们的程序就会崩溃),其次保证我们传入的类型合适。就这么简单。
现在,让我们来实现一种绘制X和O的方法。打开CApp_OnRender,就在网格后面添加如下代码:
for(int i = 0;i < 9;i++) {
int X = (i % 3) * 200;
int Y = (i / 3) * 200;
if(Grid[i] == GRID_TYPE_X) {
CSurface::OnDraw(Surf_Display, Surf_X, X, Y);
}else
if(Grid[i] == GRID_TYPE_O) {
CSurface::OnDraw(Surf_Display, Surf_O, X, Y);
}
}
这个与往常相比貌似有点复杂。首先,我们遍历网格中的每一个单元。然后我们把网格的ID号转译为X和Y坐标。我们用两种不同的方法来完成这些。为了得到X,我们让i对3取余。这样当i为0,我们得到0,i为1我们得到1,i为2我们得到2,i为3我们得到0等等。由于每个但都是200*200的,我们还得把它乘上200。为得到Y,我们我们除以3,这样当i为0,1,2时Y就是0;i是3,4,5时,Y就是1等等。最后同样乘以200。我建议你最好搞清楚这里究竟是怎么回事,因为这类方法对于基于砖瓦的游戏很常用。
接下来,我们要做的就是检查单元的类型,然后正确的绘制上面的表面。
现在我们绘制了表面,我们还需要一种人机交互的方法。在此我们使用鼠标消息。当用户单击一个单元,就会进行相应设置。我们要重写CEvent的一个函数来实现这点。打开CApp.h,在OnEvent后面紧接着OnExit添加下面函数:
void OnLButtonDown(int mX, int mY);
现在,打开CApp_OnEvent.cpp添加这个函数:
void CApp::OnLButtonDown(int mX, int mY) {
int ID = mX / 200;
ID = ID + ((mY / 200) * 3);
if(Grid[ID] != GRID_TYPE_NONE) {
return;
}
if(CurrentPlayer == 0) {
SetCell(ID, GRID_TYPE_X);
CurrentPlayer = 1;
}else{
SetCell(ID, GRID_TYPE_O);
CurrentPlayer = 0;
}
}
首先,我们要做的与把X和Y转译成ID刚好相反,这次是转换成ID。然后,我们要保证这个单元还没被占用,否则,就函数返回。接下来,我们检测轮到哪个玩家了,然后对此单元做相应修改,并轮流玩家。CurrentPlayer就是要指定轮到谁了的一个新变量,我们需要添加它。打开CApp.h,在网格数组下面添加这个变量:
int CurrentPlayer;
同样的,在CApp.cpp里设置此变量的默认值:
CApp::CApp() {
CurrentPlayer = 0;
Surf_Grid = NULL;
Surf_X = NULL;
Surf_O = NULL;
Surf_Display = NULL;
Running = true;
}
试着编译一下,你应该可以得到一个基本能够运行的井字游戏了。祝贺你!
接下来的事情就看你的了。我们已经有了开发自己游戏的一个相当稳定的基础,并且大部分工作都已完成。我希望你能走得更远点。试着添加一个“X赢了”,“O赢了”,并在每次游戏结束的时候“绘制”它们(这里需要额外的图片)。想想看,你如何检测到底是谁赢了(一个完成这个目的的函数是不是很合适)?试着在游戏完成以后重新设置它。如果你很有想法,就试着添加一些普通的AI来和玩家对战。如果你更勇敢,可以试着添加玩家对战玩家,或者玩家对战电脑的功能。
如果你都准备好了,并且对本课已经心领神会,继续进行下一课去看看帧动画吧。
SDL教程Tic Tac Toe —— 课程文件:
Win32: Zip, Rar
Linux: Tar(Thanks Gaten)
本系列教程来自 Dev Hub,一切解释权归原作者。我只是出自个人爱好,才翻译了本系列教程。因为本人也是个初学者,而且英语水平有限,错误难免,望各路高手指正。
本课原文地址:http://www.sdltutorials.com/sdl-tutorial-tic-tac-toe/
到了这个时候,我们已经为开发一个游戏打下了良好的基础。至此,我们已经建立了一个最基础的框架来处理一般的规则,而且建立了一个特殊的类来处理消息,还有一个处理一些表面函数的类。本课,我们就要用到全部这些东西,把他们结合起来,然后创建一个井字游戏。别担心,这东西很简单的。在上节课的基础上开始。
第一件要做的事,就是要规划我们的游戏。根据经验,我们知道井字游戏有3*3格子,你可以往这些格子里放置X或O。那么,我们就知道了需要3张图片,一个充当网格,一个充当X,一个充当O。我们并不需要重复的多个X或O,因为可以在程序里多次绘制同一张图片。让我们走出第一步。我们的网格是600*600的,还有X和O都是200*200的。
现在我们已经有图片了,我们需要用一种方法把他们加载到我们的程序里。打开CApp.h做点修改。删除测试用的表面,然后添加3个新表面。
- #ifndef _CAPP_H_
- #define _CAPP_H_
- #include <SDL.h>
- #include "CEvent.h"
- #include "CSurface.h"
- class CApp : public CEvent {
- private:
- bool Running;
- SDL_Surface* Surf_Display;
- private:
- SDL_Surface* Surf_Grid;
- SDL_Surface* Surf_X;
- SDL_Surface* Surf_O;
- public:
- CApp();
- int OnExecute();
- public:
- bool OnInit();
- void OnEvent(SDL_Event* Event);
- void OnExit();
- void OnLoop();
- void OnRender();
- void OnCleanup();
- };
- #endif
同样的,打开CApp.cpp并做相应修改。删除测试表面,再次添加3个新的。
- #include "CApp.h"
- CApp::CApp() {
- Surf_Grid = NULL;
- Surf_X = NULL;
- Surf_O = NULL;
- Surf_Display = NULL;
- 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();
- }
呵呵,猜对了,打开CApp_OnCleanup.cpp做同样修改。和前面一样,去掉测试表面,然添加3个新的。
- #include "CApp.h"
- void CApp::OnCleanup() {
- SDL_FreeSurface(Surf_Grid);
- SDL_FreeSurface(Surf_X);
- SDL_FreeSurface(Surf_O);
- SDL_FreeSurface(Surf_Display);
- SDL_Quit();
- }
现在我们就完成了表面的设定,并把他们加载到了内存。打开CApp_OnInit.cpp,做点修改。在此去掉测试表面,然后加载3个新的。确保文件名都正确。然后,把窗口尺寸改为600*600,也就是网格的大小。这样就可以填满整个窗口而不会在窗口周围用不到的地方留下空白。
- #include "CApp.h"
- bool CApp::OnInit() {
- if(SDL_Init(SDL_INIT_EVERYTHING) < 0) {
- return false;
- }
- if((Surf_Display = SDL_SetVideoMode(600, 600, 32, SDL_HWSURFACE | SDL_DOUBLEBUF)) == NULL) {
- return false;
- }
- if((Surf_Grid = CSurface::OnLoad("./gfx/grid.bmp")) == NULL) {
- return false;
- }
- if((Surf_X = CSurface::OnLoad("./gfx/x.bmp")) == NULL) {
- return false;
- }
- if((Surf_O = CSurface::OnLoad("./gfx/o.bmp")) == NULL) {
- return false;
- }
- return true;
- }
或许你已经注意到了我在文件名上做了点修改。我在文件名前添加了./gfx/来指定图片所在的文件夹。在游戏增长之前,一定确定实际上的一个文件夹包含这些文件。正因为此,从此以后,所有的图片将会被放到gfx文件夹。
现在,我们就把网格显示到屏幕上。打开CApp_OnRender.cpp,把测试表面的渲染改成渲染网格。
- #include "CApp.h"
- void CApp::OnRender() {
- CSurface::OnDraw(Surf_Display, Surf_Grid, 0, 0);
- SDL_Flip(Surf_Display);
- }
试试编译下你的程序,如果成功,你应该可以看到网格被显示出来了。一定要记得,使用一个表面一般只要5步:声明、设置为NULL、加载、绘制、然后清理。最好你现在就牢牢地记住这5步,因为假如一会你忘了某一步的话就会有问题了。例如,你忽略了把一个表面设置为NULL,就会导致未定义。如果你忽略了释放一个表面,就会导致内存泄漏。
或许你会感觉我们的图片有点奇怪,怎么X和O都是紫色的背景。当然有原因了,我们要对这些表面实现透明效果。其实,只要是显示紫色的地方我们会把紫色做成透明的。SDL提供了一个简单的函数来完成这个效果,SDL_SetColorKey。为了实现它,打开CSurface.h添加一个新函数。
- #ifndef _CSURFACE_H_
- #define _CSURFACE_H_
- #include <SDL.h>
- class CSurface {
- public:
- CSurface();
- public:
- static SDL_Surface* OnLoad(char* File);
- static bool OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y);
- static bool OnDraw(SDL_Surface* Surf_Dest, SDL_Surface* Surf_Src, int X, int Y, int X2, int Y2, int W, int H);
- static bool Transparent(SDL_Surface* Surf_Dest, int R, int G, int B);
- };
- #endif
现在就实现这个函数,打开CSurface.cpp然后添加它:
- bool CSurface::Transparent(SDL_Surface* Surf_Dest, int R, int G, int B) {
- if(Surf_Dest == NULL) {
- return false;
- }
- SDL_SetColorKey(Surf_Dest, SDL_SRCCOLORKEY | SDL_RLEACCEL, SDL_MapRGB(Surf_Dest->format, R, G, B));
- return true;
- }
注意下我们传递的除了表面多出的3个参数。有3个我们要做透明的颜色值,并不仅仅是紫色。举个例子,如果我们要把红色做成透明的,那就是 255,0,0。
这个函数首先检查我们是不是传递了有效的表面。如果是,我们为一个颜色设置一个颜色键(透明)。第一个参数就是我们我应用颜色键的表面,第二个是告诉SDL如何进行操作的一些标志,然后第三个参数是要做透明的颜色。这些用到的标志都很基础,第一个告诉SDL在一个源(就是传递进去的表面)上应用颜色键,第二个告诉SDL试着用RLE加速(其实就是尽量绘制得更快些)。第三个参数有点复杂,为了创建一个颜色,我们用到了SDL_MapRGB。这个函数使用一个表面,还需要指定颜色值(R,G,B),然后试着在表面上去找一个最接近的颜色值来匹配它。你或许在想,这有什么用。并不是所有的表面都有同样的调色板。还记得早期的NES年代里只有很少一些颜色值可以用么?同样的,SDL_MapRGB使用一个颜色值,然后在一个表面的调色板里找一个最接近的值。
现在就让我们对我们的表面应用这个新函数,打开CApp_OnInit.cpp,然后做如下改变:
- #include "CApp.h"
- bool CApp::OnInit() {
- if(SDL_Init(SDL_INIT_EVERYTHING) < 0) {
- return false;
- }
- if((Surf_Display = SDL_SetVideoMode(600, 600, 32, SDL_HWSURFACE | SDL_DOUBLEBUF)) == NULL) {
- return false;
- }
- if((Surf_Grid = CSurface::OnLoad("./gfx/grid.bmp")) == NULL) {
- return false;
- }
- if((Surf_X = CSurface::OnLoad("./gfx/x.bmp")) == NULL) {
- return false;
- }
- if((Surf_O = CSurface::OnLoad("./gfx/o.bmp")) == NULL) {
- return false;
- }
- CSurface::Transparent(Surf_X, 255, 0, 255);
- CSurface::Transparent(Surf_O, 255, 0, 255);
- return true;
- }
表面的一切都已搞定。下面我们要做的就是用一种方式来绘制X和O。我们可以在网格的任何地方绘制他们,因为他们并不总是在同一点。下面我们要做的就是创建一个9个容器的数组,由数组里的数据告诉我们网格上每个单元的值。于是0点就是左上,1就是中上,2就是右上,等等。把创建的数组添加到CApp.h:
- #ifndef _CAPP_H_
- #define _CAPP_H_
- #include <SDL.h>
- #include "CEvent.h"
- #include "CSurface.h"
- class CApp : public CEvent {
- private:
- bool Running;
- SDL_Surface* Surf_Display;
- private:
- SDL_Surface* Surf_Grid;
- SDL_Surface* Surf_X;
- SDL_Surface* Surf_O;
- private:
- int Grid[9];
- public:
- CApp();
- int OnExecute();
- public:
- bool OnInit();
- void OnEvent(SDL_Event* Event);
- void OnExit();
- void OnLoop();
- void OnRender();
- void OnCleanup();
- };
- #endif
我们知道,每个单元可取的值包括:空,X和O。它们告诉我们一个单元里目前是什么。为了比0,1,2来得更整洁点,我们在此使用一个enum来代替。如果你不明白enum是怎么回事,可以去找点关于这方面的速成看一下。只要知道GRID_TYPE_NONE = 0,GRID_TYPE_X = 1,GRID_TYPE_O = 2就行了。回到CApp.h,在网格数组下面添加如下:
enum {
GRID_TYPE_NONE = 0,
GRID_TYPE_X,
GRID_TYPE_O
};
注意,目前为止我一旦提到不同文件里的代码都是把它们全都贴出来。从现在开始,我希望你能清除代码进行到哪儿了。多数时候我会告诉你该往哪里添加它,有时我也会把它们全显示出来。
现在我们已经有了实现一个单元的方法,我们就需要一种重置面板的方法。让我们在CApp.h底部创建一个新的函数Reset。
public:
void Reset();
打开CApp.cpp,在main()之前添加如下函数:
void CApp::Reset() {
for(int i = 0;i < 9;i++) {
Grid[i] = GRID_TYPE_NONE;
}
}
这个循环将把网格上所有单元都设置为GRID_TYPE_NONE,意思就是所有单元都是空的。我们要在每次程序加载的最开始这么做,所有在CApp_OnInit.cpp里调用此函数:
//…
CSurface::Transparent(Surf_X, 255, 0, 255);
CSurface::Transparent(Surf_O, 255, 0, 255);
Reset();
目前一切正常。接下来我们就要实现在屏幕上放置X和O了。通过创建一个新函数来搞定它。再次打开CApp.h,就在Reset后面添加:
void SetCell(int ID, int Type);
现在,打开CApp.cpp,添加这个函数:
void CApp::SetCell(int ID, int Type) {
if(ID < 0 || ID >= 9) return;
if(Type < 0 || Type > GRID_TYPE_O) return;
Grid[ID] = Type;
}
此函数有两个参数,第一个是要改变的单元的ID,第二个是要改变成的类型。我们需要在此限制一下,首先要保证访问数组不能越界(否则我们的程序就会崩溃),其次保证我们传入的类型合适。就这么简单。
现在,让我们来实现一种绘制X和O的方法。打开CApp_OnRender,就在网格后面添加如下代码:
for(int i = 0;i < 9;i++) {
int X = (i % 3) * 200;
int Y = (i / 3) * 200;
if(Grid[i] == GRID_TYPE_X) {
CSurface::OnDraw(Surf_Display, Surf_X, X, Y);
}else
if(Grid[i] == GRID_TYPE_O) {
CSurface::OnDraw(Surf_Display, Surf_O, X, Y);
}
}
这个与往常相比貌似有点复杂。首先,我们遍历网格中的每一个单元。然后我们把网格的ID号转译为X和Y坐标。我们用两种不同的方法来完成这些。为了得到X,我们让i对3取余。这样当i为0,我们得到0,i为1我们得到1,i为2我们得到2,i为3我们得到0等等。由于每个但都是200*200的,我们还得把它乘上200。为得到Y,我们我们除以3,这样当i为0,1,2时Y就是0;i是3,4,5时,Y就是1等等。最后同样乘以200。我建议你最好搞清楚这里究竟是怎么回事,因为这类方法对于基于砖瓦的游戏很常用。
接下来,我们要做的就是检查单元的类型,然后正确的绘制上面的表面。
现在我们绘制了表面,我们还需要一种人机交互的方法。在此我们使用鼠标消息。当用户单击一个单元,就会进行相应设置。我们要重写CEvent的一个函数来实现这点。打开CApp.h,在OnEvent后面紧接着OnExit添加下面函数:
void OnLButtonDown(int mX, int mY);
现在,打开CApp_OnEvent.cpp添加这个函数:
void CApp::OnLButtonDown(int mX, int mY) {
int ID = mX / 200;
ID = ID + ((mY / 200) * 3);
if(Grid[ID] != GRID_TYPE_NONE) {
return;
}
if(CurrentPlayer == 0) {
SetCell(ID, GRID_TYPE_X);
CurrentPlayer = 1;
}else{
SetCell(ID, GRID_TYPE_O);
CurrentPlayer = 0;
}
}
首先,我们要做的与把X和Y转译成ID刚好相反,这次是转换成ID。然后,我们要保证这个单元还没被占用,否则,就函数返回。接下来,我们检测轮到哪个玩家了,然后对此单元做相应修改,并轮流玩家。CurrentPlayer就是要指定轮到谁了的一个新变量,我们需要添加它。打开CApp.h,在网格数组下面添加这个变量:
int CurrentPlayer;
同样的,在CApp.cpp里设置此变量的默认值:
CApp::CApp() {
CurrentPlayer = 0;
Surf_Grid = NULL;
Surf_X = NULL;
Surf_O = NULL;
Surf_Display = NULL;
Running = true;
}
试着编译一下,你应该可以得到一个基本能够运行的井字游戏了。祝贺你!
接下来的事情就看你的了。我们已经有了开发自己游戏的一个相当稳定的基础,并且大部分工作都已完成。我希望你能走得更远点。试着添加一个“X赢了”,“O赢了”,并在每次游戏结束的时候“绘制”它们(这里需要额外的图片)。想想看,你如何检测到底是谁赢了(一个完成这个目的的函数是不是很合适)?试着在游戏完成以后重新设置它。如果你很有想法,就试着添加一些普通的AI来和玩家对战。如果你更勇敢,可以试着添加玩家对战玩家,或者玩家对战电脑的功能。
如果你都准备好了,并且对本课已经心领神会,继续进行下一课去看看帧动画吧。
SDL教程Tic Tac Toe —— 课程文件:
Win32: Zip, Rar
Linux: Tar(Thanks Gaten)