Sam Lantinga 是 Simple DirectMedia Layer (SDL) 库的作者和 Loki Entertainment 的首席开发人员,他将向您介绍一种将游戏移植到 Linux 上的优秀工具。SDL 是一个跨平台代码移植的理想工具,它支持许多平台,如 Linux、Solaris、IRIX、FreeBSD 和 MacOS,这对于那些认为可以在 Linux 上开发商业软件的 Linux 开发者来说是一大进步。他向社区的前辈之一讨教 SDL 如何使 Linux 用户享受任何平台上最好的游戏,SDL 如何帮助开发者跟上下一代计算机游戏迷的要求。
自从 Linus 首先开发出 Linux 时开始,到现在 Linux 成为所有黑客的梦想并且遍及全世界,Linux 开发最重要的元素之一就是 OS 上游戏的质量和可用性。游戏是我们用来娱乐和休闲的。它们可以提高创造力并拓展思路。游戏还可以用来测量操作系统的性能。由于游戏越来越复杂,它们迫使每个子系统逼近极限。每当我装配一个系统时,首先要做的就是装入一个游戏并试玩,以测试每一项的性能。
Linux 上的游戏已经存在了很长时间。从早期的 NetTrek,到受高度赞扬的 DOOM! 和雷神 (Quake),人们已经可以在 Linux 上玩游戏了。但问题是没有足够的游戏。没有哪家大公司为 Linux 创作能产生轰动效应的游戏。但是,由于该操作系统变得日益流行,这种情况正开始改善。
Linux 上最早期的游戏使用 X11 协议。但是对于游戏来说,X11 实在太慢了,因为它是针对在网络上透明运行的基于菜单的应用而设计的协议。使用它的游戏通常没有绚丽的画面,而且运行得相当慢。DOOM! 是一个值得注意的例外,虽然它使用 X11,但是它通过使用 MIT 共享内存扩展可以使动画更流畅,并提供了逼真的三维效果。还有一些游戏使用 SVGA 图形库,SVGAlib。我最喜欢的一个老游戏是重力战争 (Gravity Wars),它对其模拟的老 Amiga 游戏 Gravity Force 做了重大改动。但使用 SVGAlib 的程序只能适用于少数受支持的显卡。
早期 X11 游戏,争霸 (Craft) 的图片
神话 2 (Myth 2) 的图片,Loki 出品
今天,游戏开发者有了更多的选择。仍然可以使用 X 工具箱或全屏 API,如 SVGAlib 或 fbcon,来编写游戏,但他们现在还有许多游戏库可以使用。Simple DirectMedia Layer 库是 Linux 上最好的低层游戏开发 API 之一。
SDL 是什么?
Simple DirectMedia Layer 库,简称 SDL,是为数不多的商业游戏开发公司使用的免费软件库之一。它提供跨平台的二维帧缓冲区图形和音频服务,它支持 Linux、Win32 和 BeOS。也不同程度地支持其它平台,包括 Solaris、IRIX、FreeBSD 和 MacOS。除了大量的服务,包括线程、独立于字节存储次序的宏和 CD 音频,SDL 还提供了一个简单的 API,它允许您尽可能接近本机硬件。使用 SDL 有三重优点:稳定、简单和灵活。
稳定。如果 SDL 不向 API 提供可靠的支持,那么那些爱好者和商业公司就不能使用它。因为使用了 SDL,就添加了错误修正并增强了性能,也就加强了 API 的强健性。就像内核开发是分步进行的,SDL 的开发也是分步进行的,其中一部分是可靠稳定的 API,其它部分是新功能和构思的沙箱。
简单。SDL 被设计成一个简单的 API,以最少的代码实现您的构思。比如,我最近从 Linux 演示组 Optimum 中移植了一些演示程序,我将它们的 X11 代码替换成 SDL 代码(请参见下面的列表)。您可以看到,SDL 代码非常易于编写和理解。
SDL 代码
X11 代码
代码:
int init_x (int X, int Y,
int W, int H, int bpp,
const char *Name) {
XPixmapFormatValues *formatList;
int formatCount;
int i;
int formatOk;
int scanlineLength;
XGCValues gcVal;
unsigned long gcMask;
dis = XOpenDisplay ( NULL );
if ( dis == NULL) {
fprintf ( stderr , Error :\n );
fprintf ( stderr , Cannot connect to Display.\n);
exit (1);
}
screen = DefaultScreen ( dis );
depth = DefaultDepth ( dis , screen );
width = DisplayWidth ( dis , screen );
height = DisplayHeight ( dis , screen );
winRoot = DefaultRootWindow ( dis );
winAttr.border_pixel = BlackPixel ( dis , screen );
winAttr.background_pixel = BlackPixel ( dis , screen );
winMask = CWBackPixel | CWBorderPixel;
formatList = XListPixmapFormats( dis, &formatCount);
if (formatList == NULL){
fprintf ( stderr , Cannot get pixmap list\n);
exit (1);
}
formatOk=-1;
for (i=0; ibytes_per_line*xim->height,
IPC_CREAT|0777);
xim->data = SHMInfo.shmaddr = (char *)shmat(SHMInfo.shmid, 0, 0);
SHMInfo.readOnly = False;
XShmAttach(dis, &SHMInfo);
XSync(dis, False);
buffer=(unsigned char *)xim->data;
#else
buffer = (unsigned char *)calloc(W*H, pixmapFormat.bits_per_pixel/8);
xim = XCreateImage ( dis , CopyFromParent , depth , ZPixmap , 0 ,
(char *) buffer , W , H ,
pixmapFormat.scanline_pad, scanlineLength);
if (xim == NULL){
fprintf(stderr, Couldnt create Ximage..\n);
exit(-1);
}
#endif
gcVal.foreground = 0;
gcVal.background = 0;
gcMask = GCForeground | GCBackground;
gc = XCreateGC ( dis , win , gcMask , &gcVal );
if (depth==24)
depth = pixmapFormat.bits_per_pixel;
return (depth);
}
int init_x (int X, int Y,
int W, int H, int bpp,
const char *Name) {
XPixmapFormatValues *formatList;
int formatCount;
int i;
int formatOk;
int scanlineLength;
XGCValues gcVal;
unsigned long gcMask;
dis = XOpenDisplay ( NULL );
if ( dis == NULL) {
fprintf ( stderr , Error :\n );
fprintf ( stderr , Cannot connect to Display.\n);
exit (1);
}
screen = DefaultScreen ( dis );
depth = DefaultDepth ( dis , screen );
width = DisplayWidth ( dis , screen );
height = DisplayHeight ( dis , screen );
winRoot = DefaultRootWindow ( dis );
winAttr.border_pixel = BlackPixel ( dis , screen );
winAttr.background_pixel = BlackPixel ( dis , screen );
winMask = CWBackPixel | CWBorderPixel;
formatList = XListPixmapFormats( dis, &formatCount);
if (formatList == NULL){
fprintf ( stderr , Cannot get pixmap list\n);
exit (1);
}
formatOk=-1;
for (i=0; ibytes_per_line*xim->height,
IPC_CREAT|0777);
xim->data = SHMInfo.shmaddr = (char *)shmat(SHMInfo.shmid, 0, 0);
SHMInfo.readOnly = False;
XShmAttach(dis, &SHMInfo);
XSync(dis, False);
buffer=(unsigned char *)xim->data;
#else
buffer = (unsigned char *)calloc(W*H, pixmapFormat.bits_per_pixel/8);
xim = XCreateImage ( dis , CopyFromParent , depth , ZPixmap , 0 ,
(char *) buffer , W , H ,
pixmapFormat.scanline_pad, scanlineLength);
if (xim == NULL){
fprintf(stderr, Couldnt create Ximage..\n);
exit(-1);
}
#endif
gcVal.foreground = 0;
gcVal.background = 0;
gcMask = GCForeground | GCBackground;
gc = XCreateGC ( dis , win , gcMask , &gcVal );
if (depth==24)
depth = pixmapFormat.bits_per_pixel;
return (depth);
}
灵活。返回到上面的 Optimum 演示代码示例,只要移植到 SDL,并确定一些数据假设,那么根本不必改动代码,演示就可以在 Win32、BeOS 和 Linux 控制台上运行了。灵活性的另一方面体现在尽管代码完全是跨平台的,但不会把您和底层实现隔开。SDL 提供了函数 SDL_GetWMInfo(),该函数可以让您访问底层驱动程序的专用窗口信息。Loki Entertainment Software 广泛使用这一技术为它们的游戏智能窗口管理器交互。
Optimum 演示版的屏幕快照
SDL 的起源
SDL 在两年前问世,当时我正致力于从 Macintosh 仿真器 Executor 到 Win32 的移植工作。我注意到同一个功能在几个平台上基本上都是以相同的方式实现的。所有目标都要求可以访问屏幕、映射鼠标和键盘输入,以及播放声音。我想:为什么不写一个跨平台的库来提供这些许多人会用到的基本服务?应用程序开发者只需要写一个 API,这样就大大简化了他们的工作,还可以使他们的代码可以迅速在多个平台上运行,以吸引尽可能多的观众。我开始着手进行,一年多以后,第一个稳定的 SDL 发行版诞生了。
创建 SDL 的第一步就是要标识所需的功能。根据分析 Executor 和以往的移植经验,我已经有了一个好的想法。下一步就是构建在 Win32 和 Linux 上运行的有效原型。我知道对于 API,最大的考验就是在实际的应用程序下工作,因此我所做的第一件事就是使用它来完成从 Executor 到 Win32 的移植。在这段时间里,我不断添加 API 的主体,并将它移植到 BeOS。然后,采用最新得到的 DOOM! 源码,在短短三天时间内,就使它可以在所有三个受支持的平台上顺畅地运行。
一年以后,我现在使用它来将世界上最好的一些游戏移植到世界上最受欢迎的操作系统 Linux 上。虽然我就职于商业公司,但这个库本身是免费的,并且它是开放源码概念力量的体现。
相关的库
SDL 是一个可用的大型工具集。作为一个视频 API,SDL 提供了一个简单的帧缓冲区,适用于定制位图例程或特殊效果。使用 SDL 可以完成一个完整的具有特殊演示效果和视觉享受的档案文件,但您也可以试一下 GGI 和 PTC 库,它们在游戏和演示界也是非常流行的。作为声音 API,SDL 支持自动音频转换和透明 ESound 支持。在示例档案中,甚至还有一个完整的样本混音实现,但您还可以试用一下 ALSA 和 GSI 库。对于三维图像生成,使用 Mesa 绝对是好办法。作为窗口化工具箱,GTk 非常流行。Linux 中的自由,其中一部分是选择的自由,贡献的自由。它是 Linux 理想和自由的证明。 向 SDL 贡献
SDL 按 GNU Lesser General Public License 提供,这意味着免费软件和商业程序都可以使用它,并且对源代码的更改加到公共源码之后,每个人都可以共享这个更改。欢迎向 SDL 贡献,将补丁发送给我以加到 SDL 的开发版本中。为开发者提供了一个邮件列表,可以通过将含有 subscribe sdl 字样的邮件发送到 majordomo@lokigames.com 来订阅该邮件列表。该邮件列表镜像为 news://news.lokigames.com/loki.open-source.sdl 上的一个新闻组。加入这个邮件列表以获取关于使用 SDL 的提示,错误修正的最新公告和新的功能,及使用 SDL 的项目的预览版本。 SDL 的未来
以下是下年度中 SDL 某些新令人振奋的发展方向:
游戏控制杆支持。Linux 的最新内核中构建了一个非常棒的游戏控制杆 API。我打算将该支持集成到 SDL 事件模型中,它允许任意事件过滤、回调和异步事件分派。Garrett Banuk 已经编写了最始实现,它将添加到最新的 SDL 开发分支。
Linux 帧缓冲区控制台支持。最新的 Linux 内核以帧缓冲区驱动程序的形式包含了本机图形的控制台支持,其创作者是 Geert Uytterhoeven。SDL 有一个初始驱动程序具有本机控制台键盘和 PS/2 鼠标驱动程序,它可以执行该项支持。
SciTech MGL 支持。SciTech 有一个非常棒的用于 Win32、DOS、Linux、OS/2 和 QNX 的图形库。但它是一个商业库,而 VESA 控制台驱动程序对于 Linux 是免费的。因此,可以支持 SDL 中的 VESA 控制台驱动程序。
Mesa 集成为一个 GLX 层。OpenGL 绝对是将来在 Linux 上开发三维游戏的方法。但是,GLX API 处理事件的方法不如 SDL 那样灵活。有人要求将类似于 GLX 的扩展集成到 SDL,以将它用作基于 GL 的游戏的输入接口。
重新设计体系结构以允许多重显示和多重输入。当前的体系结构将 SDL 函数以存根模块编译,并在运行时动态装入低级驱动程序。这种跨平台执行方式将导致一个难于使用的构建过程,并产生一个复杂的头文件。对我来说,最要紧的一件事就是重新设计构建过程,这样所有驱动程序都可以编译到一个库中,然后在运行时动态选择。这样做的好处就是可以同时有多个活动的显示和输入方法。想像一下游戏同时在控制台和远程 X11 显示器上运行!
更多游戏!SDL 的改进将使越来越多的人使用这个库,而越来越多的游戏将会出现在 Linux 上。在 http://www.devolution.com/~slouken/SDL/intro/toc.html 上可以找到 SDL API 的非常好的介绍,以及使用 SDL API 的项目列表。
能够涉足 Linux 游戏,我感到非常激动,我希望能够继续工作为 Linux 世界带来欢乐,因为 Linux 待我太好了。正如他们所说的,企鹅也喜欢寻开心。
我很快会写一篇后续文章,它将带给您有关如何使用 SDL 写游戏的教程。
关于作者
Sam Lantinga 是 Simple DirectMedia Layer 库的作者,现在是 Loki Entertainment Software 的首席程序员,这家公司致力于生产最畅销的 Linux 游戏。他与 Linux 和游戏打交道是从 1995 年开始的,从事各种 DOOM! 工具移植,以及将 Macintosh 游戏 Maelstrom 移植到 Linux。现在,他正在移植铁路大亨 2 (Railroad Tycoon II),这是个大量使用 SDL 技术的游戏,很快将由 Loki Entertainment Software 出品。