SDL系列教程:第一章 开始使用SDL

本博客作者保留翻译著作权,转载请联系作者。
原著链接:PacktPub.SDL.Game.Development.Jun.2013.pdf

为什么选择SDL?

不同的操作系统都有自己的绘图方式,自己的一套事件处理机制,而SDL则为我们提供了一套统一的接口来调用。这样我们只需要集中精力开发游戏,而不需要为游戏的可移植性担心。游戏编程是一项十分复杂的工作。但是SDL则极大的减轻了我们的工作量,加快游戏的开发速度。

在Windows下面开发一个游戏,然后在Linux或者OS X上面运行,SDL则提供了这样一次开发四处运行的特性。

SDL拥有庞大的用户群体帮助更新和维护,同时SDL社区也是十分活跃,所以你可以经常访问一下SDL的官网看看有没有最新的版本。

总的来说,SDL提供了一套很完善的游戏开发库,让你专注于游戏本身,而不是再为不同的平台所烦恼。

SDL2.0是什么?

SDL2.0是SDL1.2的更新版本,后者仍然在维护,2.0则提供了一些新的功能,这些新的功能详见wiki.libsdl.org/moin.cgi/Roadmap,主要有以下特点:

  • 3D图像的加速渲染
  • 2D硬件加速
  • 支持Render Target
  • 支持多窗口
  • 提供对剪贴板访问的API
  • 支持多个输入设备
  • 支持7.1 声道
  • 支持多个音频设备
  • 提供对力反馈操纵杆的API
  • 支持水平滚轮鼠标
  • 提供多点触控的API
  • 支持音频捕获
  • 对多线程的改进

虽然以上特性我们后面的游戏编程不一定都会用到,但是这些新的特性都会使SDL成为一个良好的游戏框架。在后面我们将会用到里面的2D硬件加速来使我们的游戏拥有出色的表现。

SDL2.0 扩展库

SDL 框架有一些单独的扩展库,我们可以使用这些库来使我们的游戏变得更加完善,这些扩展库不放在前面讲述是尽量让SDL框架不显得那么臃肿。只有在我们有必要的时候,才会考虑使用这些扩展库。下面列举了一些十分常用的扩展库以及他们的用途,这些扩展库已经从原来的SDL1.2/1.3更新到支持最新的SDL 2.0:

  • SDL_image  这是一个用于加载图像的库,支持BMP,GIF,PNG,TGA,PCX以及其他格式。

  • SDL_net   这是一个跨平台的网络支持库。

  • SDL_mixer  这是一个音频支持库,他支持MP3,MIDI,OGG这三种格式。

  • SDL_ttf   这个库可以让你在游戏当中使用TrueType字体。

  • SDL_rtf   这是一个可以让SDL支持富文本渲染的库。

在Visual C++ Express 2010当中配置SDL

下面我们会讲解如何在Visual C++ Express当中配置SDL2.0,Visual C++ Express这个IDE他为我们提供了一个免费的游戏开发环境,当然你用Visual Studio也是可以的,因为VS包含了VC++。Visual C++ Express可以在这个地方免费下载,如果说你已经成功安装了这个IDE,那么我们就可以去下载SDL2.0,如果你不使用Visual C++ Express来开发你的游戏或者说你不打算在Windows下面开发的话,后面的这些步骤你可能需要根据实际情况做一些调整。

SDL 2.0还在发展,所以现在还没有正式发布,你可以有两种方式来获得源码:

  • 你可以下载最新源码的快照,来快速构建你的游戏。(最简单)
  • 你也可以使用mercurial来clone源码。(能够与最新版本保持一致)
    这两种方式可以在这里找到:http://www.libsdl.org/hg.php

当然,在Windows当中使用SDL2.0开发,必须使你的Direct X SDK保持最新版本,你可以在[这里](http://www.microsoft.com/en-gb/download/details.
aspx?id=6812)找到最新的SDK。

在Windows下使用Mercurial获得SDL2.0

直接从不断更新的代码库来获得SDL2.0是最好的方式,可以确保你的SDL2.0是最新版本,任何的错误修复都会在上面第一时间提交。关于Mercurial的GUI工具有很多,这里我们使用tortoisehg.bitbucket.org提供的程序,我们安装了这个程序之后就能够即时的获取到最新的SDL2.0版本。

建立一个新的SDL2.0克隆

  • 1.打开TortoiseHg Workbench窗口:
    step1.png
  • 2.按下Ctrl + Shift + N 来打开克隆对话框。
  • 3.输入远程仓库的源路径,这里我们直接填入:http://hg.libsdl.org/SDL即可。
  • 4.输入或者按下"Browse"(浏览)选择一个本地路径作为SDL2.0的本地仓库,如C:\SDL2。
  • 5.点击"Clone"按钮,并且允许远程库复制到本地。
    2.png
  • 6.在C:\SDL2的文件夹下面会有一个VisualC的文件夹,里面会有一个Visual C++ 2010的解决方案,我们使用Visual C++ Express 来打开。
  • 7.打开的时候可能会提示一些警告信息,这是由于不同版本解决方案所造成的,这些可以忽略掉,并不影响我们SDL2.0的使用。
  • 8.根据你的系统来改变构建配置。
    3.png
  • 9.右键点击SDL,选择"Build"(构建)。
  • 10.现在我们便有了一个SDL 2.0的库,它位于C:
    SDL2\VisualC\SDL\Win32(or x64)\Release\SDL.lib这个位置。
  • 11.我们还需要构建SDL的主库文件,所以在"SDLmain"上面右键,点击"Build"(构建),这个文件位于: C:\SDL2\VisualC
    SDLmain\Win32(or x64)\Release\SDLmain.lib
  • 12.创建一个文件夹,重命名为lib,将C:\SDL2里面的SDL.lib和SDLmain.lib这两个文件复制进去。

我现在有了SDL库,还需要做什么?

现在我们的Viusal C++ 2010 创建工程之后就可以和SDL库进行链接,下面就是链接步骤:

  • 1.在Visual C++ Express里面创建一个新的工程并为其命名,例如SDL-Game。

  • 2.创建之后在"Solution Explorer"(解决方案资源管理器)当中右键工程,点击属性。

  • 3.在"Configuration"的下拉列表当中选择"All Configurations"。

  • 4.在VC++ Directories里面选择"Include Directories"(包含目录)这个项目,双击他。
    4.png

  • 5.在里面你可以选择一个新位置,你可以选择C:\SDL2\include,并点击确定。

  • 6.这一步则是做同样的事情,不过是在"library directories"(库目录)选项内添加我们库文件所在的目录。(C:\SDL2\lib)

  • 7.选择"Linker"(链接器),在这个Linker下面会有一个"Input"(输入),点击进去会看到有一个"Additional Dependencies type"(附加依赖项),在附加依赖项里面我们添加上SDL.lib和SDLmain.lib,记得每个要换行哦。
    5.png

  • 8.也是在"Linker"下面选择"System"(系统)选项,然后在"SubSystem"(子系统)里面选择"Windows(/SUBSYSTEM:WINDOWS)"(窗口(/SUBSYSTEM:WINDOWS))
    6.png

  • 9.点击确定即完成了我们所有的操作。

Hello SDL

我们的空项目通过上面的步骤已经引入了SDL的库,所以是时候开始进行游戏开发了。点击"Source Files"(源文件),右键添加新项目(或者使用Ctrl+Shift+A),选择cpp源文件,命名为main.cpp。创建完成之后在里面键入以下代码:

#include <SDL.h>

SDL_Window *g_pWindow=0;
SDL_Renderer *g_pRenderer=0;

int main(int argc,char *args[])
{
    // 初始化SDL
    if(SDL_Init(SDL_INIT_EVERYTHING) >= 0)
    {
        // 初始化成功后创建一个窗口
        g_pWindow = SDL_CreateWindow("Chapter 1:Setting up SDL",
        SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED,
        640,480,
        SDL_WINDOW_SHOWN);
        
        // 窗口创建成功后,创建一个渲染器
        if(g_pWindow != 0)
        {
            g_pRenderer = SDL_CreateRenderer(g_pWindow,-1,0);
        }
    }
    else
    {
        return 1; // SDL初始化失败
    }

    // 当所有初始化工作成功后,我们将渲染器颜色置为黑色
    // 该函数支持Alpha通道
    SDL_SetRenderDrawColor(g_pRenderer,0,0,0,255);

    // 清空屏幕
    SDL_RenderClear(g_pRenderer);

    // 刷新屏幕
    SDL_RenderPresent(g_pRenderer);
    
    // 延迟5秒退出
    SDL_Delay(5000);
    
    // 退出
    SDL_Quit();

    return 0;
}

现在我们可以尝试构建我们第一个SDL程序了,右键点击该项目,选择"Build"(构建)。会提示一个错误,说没有找到SDL.dll文件:
QQ截图20160218122818.png
在尝试"Release"或者"Debug"构建你的程序时,应该在你Visual Studio的工程目录下将SDL.dll复制到对应的文件夹里面。(Release 或者 Debug文件夹内)这个DLL文件通常位于C:\SDL2\VisualC\SDL\Win32(或者x64)\Release\SDL.dll,当你想发布你的游戏的时候,需要将SDL.dll放在你exe的文件夹内。现在我们编译我们的程序,运行之后会显示一个SDL窗口,并且在5秒后关闭。

Hello SDL 的概述

我们来看看Hello SDL 的代码:
1.首先,我们包含了SDL.h 的头文件,这样我们就获得了SDL的所有功能函数。

#include <SDL.h>

2.然后我们创建了一些全局变量,一个是SDL_Window指针,通过SDL_CreateWindow函数赋值来初始化,还有一个是SDL_Renderer指针,通过SDL_CreateRenderer函数赋值来初始化:

SDL_Window* g_pWindow = 0;
SDL_Renderer* g_pRenderer = 0;

3.现在我们可以来初始化SDL,这个实例初始化SDL子系统使用的是SDL_INIT_EVERYTHING标志,但是这不是必须的。(参见SDL初始化标志)

int main(int argc,char args[])
{
    // 初始化SDL
    if(SDL_Init(SDL_INIT_EVERYTHING) >= 0)
    {

4.如果SDL初始化成功了的话,我们便可以对g_pWindow 进行初始化。SDL_CreateWindow函数将会根据我们所提供参数来创建一个窗口,并且返回一个SDL_Window指针。函数第一个参数是窗口的标题,第二参数是窗口出现的位置,第三个参数是窗口的宽度,第四个参数是窗口的高度,以及所需要的SDL初始化标志(在本章的后面将会讲解这些标志的作用)。SDL_WINDOWPOS_CENTERED即是让我们的窗口的位置将会在屏幕的中心:

// if succeeded create our window
g_pWindow = SDL_CreateWindow("Chapter 1: Setting up SDL", SDL_
WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, SDL_WINDOW_
SHOWN);

5.完成以上步骤以后,为了程序的健壮性,我们需要检测一下窗口创建是否成功。如果窗口创建成功的话,那么我们会继续初始化我们的渲染器。SDL_CreateRenderer函数第一个参数是目标窗口,是一个SDL_Window指针,这里我们传入g_pWindow指针,第二个参数是渲染器的驱动索引,我们传入-1来确保我们的渲染器是第一个驱动的,最后一个是标志,这里我们使用SDL_RendererFlag(见SDL渲染标志):

// if the window creation succeeded create our renderer
if(g_pWindow != 0)
{
g_pRenderer = SDL_CreateRenderer(g_pWindow, -1, 0);
}
else
{
return 1; // sdl could not initialize
}

6.如果一切顺利,现在我们将创建并显示窗口:

// everything succeeded lets draw the window
// set to black
SDL_SetRenderDrawColor(g_pRenderer, 0, 0, 0, 255);
// clear the window to black
SDL_RenderClear(g_pRenderer);
// show the window
SDL_RenderPresent(g_pRenderer);
// set a delay before quitting
SDL_Delay(5000);
// clean up SDL
SDL_Quit();

SDL初始化标志

对于事件处理、文件I/O以及多线程这些子系统都使用默认初始化,其他的子系统可以使用以下标志进行初始化:

  • SDL_INIT_HAPTIC  力反馈子系统
  • SDL_INIT_AUDIO  音频子系统
  • SDL_INIT_VIDEO  视频子系统
  • SDL_INIT_TIMER  时钟子系统
  • SDL_INIT_JOYSTICK  操纵杆子系统
  • SDL_INIT_EVERYTHING  所有子系统
  • SDL_INIT_NOPARACHUTE  不捕获致命信号

我们也可以用位与操作符(|)来初始化两个或者多个子系统。只初始化音频和视频子系统的话,我们可以这么写:

SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO);

检测某个子系统是否已经激活,可以使用SDL_WasInit()函数:

if(SDL_WasInit(SDL_INIT_VIDEO) != 0)
{
cout << "视频子系统未初始化";
}

SDL渲染标志

我们在初始化一个SDL_Renderer的时候,我们可以通过一个标志来确定其行为:

  • SDL_RENDERER_SOFTWARE  使用软件渲染
  • SDL_RENDERER_ACCELERATED  使用硬件加速
  • SDL_RENDERER_PRESENTVSYNC  渲染器与屏幕刷新率同步
  • SDL_RENDERER_TARGETTEXTURE  支持渲染纹理

如何制作一个游戏

在设计一个游戏的时候,本质上是各子系统之间的相互作用,如图像子系统、游戏逻辑、与用户输入。图像子系统不应该干涉游戏逻辑的实现,反之亦然,我们认为一个游戏的结构应该如下:
无标题.png

一旦游戏被初始化,它就开始一个循环用于检测用户的输入。用户退出游戏的时候,这个循环会跳出,并且清理掉所有资源。这是一个游戏编程的基本框架,在本书当中我们也会遵循以上框架进行编程。

我们将会构建一个可重用的框架,这样在创建新项目的时候我们可以节省大量的时间。现在,我们将会重构之前的Hello SDL代码,将分成多个单独的部分。这个过程有助于我们思考如何将代码分解成独立的模块,而不是将所有的代码都放在一个文件里面,显得杂乱无章。

分离Hello SDL的代码

我们可以将之前的代码根据不同的功能来封装成多个函数:

bool g_bRunning = false; // 游戏运行状态

按照下面的步骤来逐步分解我们的代码:

  1. 创建一个init函数用来初始化我们的游戏窗口,SDL_CreateWindow所需要的参数在init当中封装一次。
bool init(const char* title,int xpos,int ypos,int height,int width,int flags)
{
    // 初始化SDL
    if(SDL_Init(SDL_INIT_EVERYTHING >= 0)
    {
        g_pWindow = SDL_CreateWindow(title,xpos,ypos,height,width,flags);
        // 判断是否成功创建了窗口
        if(g_pWindow != 0)
        {
            
}