SDL 笔记

开发环境配置

http://tjumyk.github.io/sdl-tutorial-cn/contents.html

VS 新建项目选择控制台程序,链接属性中选择子系统“/SUBSYSTEM:WINDOWS”。

在屏幕上显示一张图片

#include <SDL/SDL.h>

int main(int argc, char *args[])
{
  SDL_Surface *hello = nullptr;
  SDL_Surface *screen = nullptr;

  SDL_Init(SDL_INIT_EVERYTHING);

  // 设置窗口
  screen = SDL_SetVideoMode(640, 480, 32, SDL_SWSURFACE);

  // 加载图像
  hello = SDL_LoadBMP("hello.bmp");

  // 将图像应用到窗口上
  SDL_BlitSurface(hello, nullptr, screen, nullptr);

  // 更新窗口
  SDL_Flip(screen);

  SDL_Delay(2000);

  // 释放资源
  SDL_FreeSurface(hello);
  SDL_Quit();
  return 0;
}

优化表面的加载和 Blit

#include <SDL/SDL.h>
#include <string>

const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
const int SCREEN_BPP = 32;

SDL_Surface *message = nullptr;
SDL_Surface *background = nullptr;
SDL_Surface *screen = nullptr;

SDL_Surface* load_image(const std::string &filename)
{
  SDL_Surface *loadedImage = nullptr;
  SDL_Surface *optimizedImage = nullptr;

  // 这个位图是24位的,而窗口是32位的,将一个表面blit到另一个不同表面上时,
  // SDL会在每次blit时做一次临时性的格式转换,这会导致程序运行效率降低。
  loadedImage = SDL_LoadBMP(filename.c_str());

  if (loadedImage != nullptr) {
    // 创建一个与窗口同样格式的图像
    optimizedImage = SDL_DisplayFormat(loadedImage);
    // 释放资源
    SDL_FreeSurface(loadedImage);
  }

  return optimizedImage;
}

void apply_surface(int x, int y, SDL_Surface *source, SDL_Surface *destination)
{
  SDL_Rect offset;
  offset.x = x;
  offset.y = y;

  SDL_BlitSurface(source, nullptr, destination, &offset);
}

int main(int argc, char *args[])
{
  if (SDL_Init(SDL_INIT_EVERYTHING) == -1) {
    return 1;
  }

  screen = SDL_SetVideoMode(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP, SDL_SWSURFACE);

  if (screen == nullptr) {
    return 1;
  }

  // 设置窗口标题
  SDL_WM_SetCaption("Hello World", nullptr);

  // 加载图片
  message = load_image("hello.bmp");
  background = load_image("background.bmp");

  // 将图片应用到窗口上
  // SDL的坐标系左上角是原点
  apply_surface(0, 0, background, screen);
  apply_surface(320, 0, background, screen);
  apply_surface(0, 240, background, screen);
  apply_surface(320, 240, background, screen);
  apply_surface(180, 140, message, screen);

  if (SDL_FLip(screen) != -1) {
    return 1;
  }

  SDL_Delay(2000);

  SDL_FreeSurface(message);
  SDL_FreeSurface(background);
  SDL_Quit();

  return 0;
}

SDL仅原生地支持bmp格式的图片文件,但是通过使用SDL_image这个扩展库,你将可以加载BMP, PNM, XPM, LBM, PCX, GIF, JPEG, TGA 和 PNG 格式的图片文件。

//加载图像
loadedImage = IMG_Load(filename.c_str());

事件驱动编程

bool quit(false);
SDL_Event event;
while (quit == false) {
  while (SDL_PollEvent(&event)) {
    if (event.type == SDL_QUIT) {
      quit = true;
    }
  }
}

其他相关函数:SDL_WaitEvent() SDL_PeepEvents()

错误处理相关函数:SDL_GetError() IMG_GetError()

指定颜色为透明

// 将所有颜色为(R 0, G 0xFF, B 0xFF)的像素设为透明
Uint32 colorKey = SDL_MapRGB(optimizedImage->format, 0, 0xFF, 0xFF);
SDL_SetColorKey(optimizedImage, SDL_SRCCOLORKEY, colorKey);

SDL_SRCCOLORKEY标志保证了关键色仅当blit这个表面到另一个表面上时才被使用。

对于带alpha通道的PNG图片,IMG_Load()函数会自动地为他们处理透明色。在一个已经具有透明背景色的图片上设置关键色会导致糟糕的结果。另外,如果你使用SDL_DisplayFormat(),而不是SDL_DisplayFormatAlpha(),你也会丢失Alpha透明色。要保持PNG中的透明色,请不要设置关键色。IMG_Load()也会处理TGA图像的Alpha透明色。你可以在SDL的文档里得到更详细的有关关键色的信息。

局部Blit和精灵图

精灵图是一系列保存在同一个图像文件中的图像。当你有数量庞大的图像,但不想处理那么多的图像文件时,精灵图就派上用场了。

//用白色填充窗口
SDL_FillRect(screen, &screen->clip_rect, SDL_MapRGB(screen->format, 0xFF, 0xFF, 0xFF));

SDL_Rect clip[4];
//左上角的剪切区域
clip[0].x = 0;
clip[0].y = 0;
clip[0].w = 100;
clip[0].h = 100;

//右上角的剪切区域
clip[1].x = 100;
clip[1].y = 0;
clip[1].w = 100;
clip[1].h = 100;

//左下角的剪切区域
clip[2].x = 0;
clip[2].y = 100;
clip[2].w = 100;
clip[2].h = 100;

//右下角的剪切区域
clip[3].x = 100;
clip[3].y = 100;
clip[3].w = 100;
clip[3].h = 100;

//将图像应用到窗口中
apply_surface( 0, 0, dots, screen, &clip[0] );
apply_surface( 540, 0, dots, screen, &clip[1] );
apply_surface( 0, 380, dots, screen, &clip[2] );
apply_surface( 540, 380, dots, screen, &clip[3] );

现在,当你想要使用很多图片时,你不必保存成千上万个图片文件。你可以将一个子图集合放入一个单独的图片文件中,并blit你想要使用的部分。

True Type 字体

SDL 本身不原生地支持 TTF 文件,所以你需要使用 SDL_ttf 扩展库。SDL_ttf 是一个能从 True Type 字体中生成表面的的扩展库。

TTF_Font *font = nullptr;
SDL_Color textColor = {255, 255, 255};

TTF_Init();

font = TTF_OpenFont("lazy.ttf", 28);
SDL_Surface *message = TTF_RenderText_Solid(font, "The quick brown fox jumps over the lazy dog", textColor);

// 清理
TTF_CloseFont(font);
TTF_Quit();

处理键盘事件

if (SDL_PollEvent(&event)) {
  if (event.type == SDL_KEYDOWN) {
    switch (event.key.keysym.sym) {
    case SDLK_UP:
      // ...
      break;
    case SDLK_DOWN:
      // ...
      break;
    }
  }
}

处理鼠标事件

if(event.type == SDL_MOUSEBUTTONDOWN) {
  if(event.button.button == SDL_BUTTON_LEFT) {
    // ...
  }
}

按键状态

在不使用事件的情况下通过按键状态检查一个按键是否被按下。

//获取按键状态
Uint8 *keystates = SDL_GetKeyState(nullptr);
if(keystates[SDLK_UP]) {
  // ...
}

其他相关函数:SDL_GetModState() SDL_GetMouseState() SDL_JoystickGetAxis()

播放声音

播放声音是游戏编程的又一个关键概念。SDL原生的声音函数可能会让人很纠结。所以,你将学习使用SDL_mixer扩展库来播放声效和音乐。SDL_mixer扩展库将使用声音变得非常容易。

对于那些使用OGG, MOD或者其他非WAV声音格式的人,请使用Mix_Init()来初始化解码器,并使用Mix_Quit()来关闭解码器。

//将要播放的音乐
Mix_Music *music = NULL;

//将要使用的声效
Mix_Chunk *scratch = NULL;
Mix_Chunk *high = NULL;
Mix_Chunk *med = NULL;
Mix_Chunk *low = NULL;

//初始化SDL_mixer
Mix_OpenAudio(22050, MIX_DEFAULT_FORMAT, 2, 4096);

//加载音乐
music = Mix_LoadMUS( "beat.wav" );
//加载声效
scratch = Mix_LoadWAV( "scratch.wav" );
high = Mix_LoadWAV( "high.wav" );
med = Mix_LoadWAV( "medium.wav" );
low = Mix_LoadWAV( "low.wav" );

Mix_PlayMusic( music, -1 );
//Mix_PauseMusic();
//Mix_ResumeMusic();
Mix_PlayChannel( -1, scratch, 0 );

// 释放资源
//释放声效
Mix_FreeChunk( scratch );
Mix_FreeChunk( high );
Mix_FreeChunk( med );
Mix_FreeChunk( low );
//释放音乐
Mix_FreeMusic( music );
//退出SDL_mixer
Mix_CloseAudio();

计时

Uint32 start = SDL_GetTicks();
// ...
std::cout << SDL_GetTicks() - start << std::endl;

其他笔记

SDL Game Development.pdf

坐标系
原点:左上角

SDL扩展
SDL_image
支持多种格式图片加载:BMP GIF PNG TGA PCX ...
SDL_net
跨平台网络库
SDL_mixer
audio mixer library. 支持MP3 MIDI OGG
SDL_ttf
支持TrueType字体
SDL_rtf
support the rendering of the Rich Text Format (RTF).

SDL_Init()
初始化标识

SDL_INIT_HAPTIC      Force feedback subsystem  力反馈
SDL_INIT_AUDIO       Audio subsystem
SDL_INIT_VIDEO       Video subsystem
SDL_INIT_TIMER       Timer subsystem
SDL_INIT_JOYSTICK    Joystick subsystem
SDL_INIT_EVERYTHING  All subsystems
SDL_INIT_noparachute Don't catch fatal signals

查看一个子系统是否已被初始化:

if (SDL_WasInit(SDL_INIT_VIDEO) != 0)
    cout << "video was initialized";
SDL_CreateRenderer()
SDL_RENDERER_SOFTWARE      Use software rendering
SDL_RENDERER_ACCELERATED   Use hardware acceleration
SDL_RENDERER_PRESENTVSYNC  Synchronize renderer update with screen's refresh rate
SDL_RENDERER_TARGETTEXTURE Supports render to texture

游戏程序结构
初始化
游戏循环:获取输入 物理运算 渲染
退出

window flags

SDL_WINDOW_FULLSCREEN      Make the window fullscreen
SDL_WINDOW_OPENGL          Window can be used with as an OpenGL context
SDL_WINDOW_SHOWN           The window is visible
SDL_WINDOW_HIDDEN          Hide the window
SDL_WINDOW_BORDERLESS      No border on the window
SDL_WINDOW_RESIZABLE       Enable resizing of the window
SDL_WINDOW_MINIMIZED       Minimize the window
SDL_WINDOW_MAXIMIZED       Maximize the window
SDL_WINDOW_INPUT_GRABBED   Window has grabbed input focus
SDL_WINDOW_INPUT_FOCUS     Window has input focus
SDL_WINDOW_MOUSE_FOCUS     Window has mouse focus
SDL_WINDOW_FOREIGN         The window was not created using SDL

程序基本结构

#include <SDL.h>

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

int main(int argc, char* argv[])
{
    // initialize SDL
    if (SDL_Init(SDL_INIT_EVERYTHING) >= 0)
    {
        // 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);

        // 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

    // everything succeeded lets draw the window
    bool bQuit = false;
    while (true)
    {
        SDL_Event event;
        if (SDL_PollEvent(&event))
        {
            switch (event.type)
            {
            case SDL_QUIT:
                bQuit = true;
                break;
            }
        }
        if (bQuit)
            break;

        // set to black
        // This function expects Red, Green, Blue and Alpha as color values
        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();

    return 0;
}

绘制图片过程

  1. 加载图片获得SDL_Surface
  2. 根据Surface获得SDL_Texture
  3. 获得纹理尺寸:SDL_QueryTexture
  4. 渲染:SDL_RenderCopy/SDL_RenderCopyEx 需要Renderer参数

SDL使用两种数据结构渲染到屏幕
SDL_Surface 像素集 使用软件渲染(not GPU)
SDL_Texture 使用硬件加速

SDL_Texture* m_pTexture
SDL_Rect m_sourceRectangle;
SDL_Rect m_destinationRectangle;

// 根据图片创建SDL_Texture
SDL_Surface* pTempSurface = SDL_LoadBMP("asserts/rider.bmp");
m_pTexture = SDL_CreateTextureFromSurface(m_pRenderer, pTempSurface);
SDL_FreeSurface(pTempSurface);

// 获得图片的大小
SDL_QueryTexture(m_pTexture, NULL, NULL,
    &m_sourceRectangle.w, &m_sourceRectangle.h);

SDL_RenderClear();
SDL_RenderCopy(m_pRenderer, m_pTexture,
    &m_sourceRectangle, &m_destinationRectangle);
SDL_RenderPresent();

源矩形 目标矩形 参数传入0 将整个纹理渲染到整个窗口。

SDL_GetTicks() 毫秒

SDL_RenderCopyEx() 支持旋转和翻转Flip

SDL_image
sdl2_image.lib

#include <sdl_image.h>
SDL_Surface* pTempSurface = IMG_Load("animate.png");

Fixed frames per second (FPS) is
not necessarily always a good option, especially when your game includes more
advanced physics. It is worth bearing this in mind when you move on from this
book and start developing your own games. Fixed FPS will, however, be fine for
the small 2D games, which we will work towards in this book.

固定帧频

const int FPS = 60;
const int DELAY_TIME = 1000.0f / FPS;  // 1000毫秒
while (...)
{
    frameStart = SDL_GetTicks();  // 毫秒
    ...
    frameTime = SDL_GetTicks() - frameStart;
    if (frameTime < DELAY_TIME)
        SDL_Delay((int)(DELAY_TIME - frameTime));
}
SDL joystick event
SDL_JoyAxisEvent   Axis motion information
SDL_JoyButtonEvent Button press and release information
SDL_JoyBallEvent   Trackball event motion information
SDL_JoyHatEvent    Joystick hat position change

SDL joystick event    Type value
SDL_JoyAxisEvent      SDL_JOYAXISMOTION
SDL_JoyButtonEvent    SDL_JOYBUTTONDOWN or
                      SDL_JOYBUTTONUP
SDL_JoyBallEvent      SDL_JOYBALLMOTION
SDL_JoyHatEvent       SDL_JOYHATMOTION

不同的游戏控制器 按钮和轴可能有不同的值 比如Xbox360 controller, PS3 controller
Xbox360 controller:
Two analog sticks
Analog sticks press as buttons
Start and Select buttons
Four face buttons: A, B, X, and Y
Four triggers: two digital and two analog
A digital directional pad

if (SDL_WasInit(SDL_INIT_JOYSTICK) == 0)
    SDL_InitSubSystem(SDL_INIT_JOYSTICK);
if (SDL_NumJoysticks() > 0)
{
    for (int i=0; i<SDL_NumJoysticks(); ++i)
    {
        SDL_Joystick* joy = SDL_JoystickOpen(i);
        if (SDL_JoystickOpened(i) == 1)
            m_joysticks.push_back(joy);
    }
    SDL_JoystickEventState(SDL_ENABLE);
}

SDL_JoystickClose(joy);

分辨是哪个控制器的事件

if (event.type == SDL_JOYAXISMOTION)
    int whichOne = event.jaxis.which;

控制器按钮
SDL_JoystickNumButtons
event.jbutton.button // 按钮ID

鼠标事件

SDL Mouse Event        Purpose
SDL_MouseButtonEvent   A button on the mouse has been pressed or released
SDL_MouseMotionEvent   The mouse has been moved
SDL_MouseWheelEvent    The mouse wheel has moved

SDL Mouse Event        Type Value
SDL_MouseButtonEvent   SDL_MOUSEBUTTONDOWN or SDL_MOUSEBUTTONUP
SDL_MouseMotionEvent   SDL_MOUSEMOTION
SDL_MouseWheelEvent    SDL_MOUSEWHEEL
// 鼠标按钮
// SDL numbers these as 0 for left, 1 for middle, and 2 for right.

if (event.type == SDL_MOUSEBUTTONDOWN)
    if (event.button.button == SDL_BUTTON_LEFT)

event.type SDL_MOUSEMOTION
event.motion.x
event.motion.y

// 键盘
1 表示按下 0 表示没有按下
SDL_GetKeyboardState(int* numkeys)
Uint8* m_keystates;
m_keystates = SDL_GetKeyboardState(0);
SDL_Scancode key;
if (m_keysttes[key] == 1)

有限状态机
需要能够处理以下情况:
Removing one state and adding another
Adding one state without removing the previous state
Removing one state without adding another

Game
GameObject
TextureManager // 负责加载图片文件,负责绘制,纹理ID
InputHandler
GameState
GameStateMachine
GameObjectFactory
StateParser

Distributed Factory
class GameObjectFactory
std::map<std::string, BaseCreator*> m_creators;
registerType(std::string typeID, BaseCreator* pCreator);

https://github.com/ReneNyffenegger/development_misc/tree/master/base64.
The base64.h and base64.cpp files can be added directly to the project.

SDL_Mixer
Mix_OpenAudio(int frequency, Uint16 format, int channels, int chunksize)
Mix_OpenAudio(22050, AUDIO_S16, 2, 4096);
std::map<std::string, Mix_Chunk*> mSfxs;
std::map<std::string, Mix_Chunk*> mMusic;

Mix_Music* music = Mix_LoadMUS(filename);  // ogg
Mix_Chunk* chunk = Mix_LoadWAV(filename);  // wav

Mix_PlayMusic()     // Mix_Music
Mix_PlayChannel()   // Mix_Chunk
Mix_CloseAudio();

SDL_SetTextureAlphaMod()