1. Hello, world

Hello, world と言ってもウィンドウを表示するだけのサンプルですが. ウィンドウの扱いを除けば, ほとんど SDL1.2 と変わりません. まずはコード*1.

#include <iostream>
#include <SDL.h>

int main(int, char**) { // 引数は省略できない.
  // SDL を初期化する.
  if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) == -1) {
    std::cerr << "SDL_Init failed:  " << SDL_GetError() << std::endl;
    return 0;
  }

  // ウィンドウを作成する.
  SDL_Window* window = SDL_CreateWindow("Hello, World!",
                                        SDL_WINDOWPOS_UNDEFINED,
                                        SDL_WINDOWPOS_UNDEFINED,
                                        640,
                                        480,
                                        SDL_WINDOW_SHOWN);
  // イベントループ
  SDL_Event event;
  for (bool quit = false; !quit;) {
    if (SDL_PollEvent(&event)) {
      switch (event.type) {
        case SDL_QUIT:
          quit = true;
          break;
        case SDL_WINDOWEVENT:
          quit = event.window.event == SDL_WINDOWEVENT_CLOSE;
          break;
        default: break;
      }
      continue;
    }
    // ここに各フレームでの処理 (描画など) を書くことになる.
    SDL_Delay(10);
  }

  SDL_Quit();
  return 0;
}

まず SDL を用いるプログラムでは main() 関数のシグネチャーは (int, char**) 固定です. これは SDL がプラットフォームごとのスタートアップ関数の差を吸収する機能に関わる制限で, SDL1.2 と同じ仕様です. たとえば Windows での GUI プログラムは WinMain() 関数がスタートアップとなりますが, SDL を使う場合にはリンクする SDLmain.lib の中に WinMain() 関数が実装されていて, ユーザーが定義した main() 関数を呼び出すようになっています. その際のシグネチャーが引数ありのものなので, ユーザーが定義する main() 関数も引数付きでないといけないわけです.

デバッグする際に一つ注意しなければならないこととして, SDL.h の中で次のようなマクロ定義がされています.

#define main SDL_main

つまり, 上のようにユーザーが定義した main() 関数は, 実際には SDL_main() という名前の関数になります.

SDL の機能は SDL_Init() 関数を呼び出してから使えるようになります. 引数には初期化したいサブシステムの種類を OR で指定します. これも SDL1.2 と同じですが, SDL1.3 ではサブシステムが増えています. 定義されている種類は SDL_INIT_TIMER, SDL_INIT_AUDIO, SDL_INIT_TIMER, SDL_INIT_JOYSTICK, SDL_INIT_HAPTIC, SDL_INIT_NONPARACHUTE, SDL_INIT_EVENTTHREAD で, これらをすべて有効にした SDL_INIT_EVERYTHING も定義されています. ちなみに現時点で SDL_INIT_EVENTTHREAD は未実装のようです.

ウィンドウの作成は SDL_CreateWindow() で行います. ウィンドウ周りは SDL1.3 で大幅に変更された部分です.

SDL_Window* SDL_CreateWindow(char const* title, int x, int y, int w, int h, Uint32 flags);
ウィンドウを作成します. title はキャプションを表し, x, y は SDL_WINDOWPOS_UNDEFINED または SDL_WINDOWPOS_CENTERED を指定します. w, h はウィンドウの解像度です. flags には SDL_WINDOW_FULLSCREEN, SDL_WINDOW_OPENGL, SDL_WINDOW_SHOWN, SDL_WINDOW_BORDERLESS, SDL_WINDOW_RESIZABLE, SDL_WINDOW_MAXIMIZED, SDL_WINDOW_MINIMIZED, SDL_WINDOW_INPUT_GRABBED を OR で指定します (どれも使用しない場合は 0 を渡す). 戻り値は名前の通り, 作成されたウィンドウ構造体へのポインターです *2. 0 の場合はエラーを表します.

SDL1.3 は複数ウィンドウ対応です. SDL1.2 と比べて, ウィンドウの作成や管理, イベントなどがかなり充実しています.

SDL_PollEvent() 関数を呼び出すと, その時点でイベントキューに要素があればそれを取り出しコピーして 1 を返し, 要素が溜まっていなければ 0 を返します. 上のコードでは, 毎フレームイベントキューをすべて消化しています.

イベントループは, 上のコードでは SDL1.2 とあまり変わっていませんが, ウィンドウの×ボタンが押された場合に SDL_QUIT イベントではなく SDL_WINDOWEVENT_CLOSE イベントが発生するようになっています. これは複数ウィンドウを使用する場合に, ×ボタンを押すことがプログラムの終了を表さないケースがあるからでしょう. SDL_WINDOWEVENT イベントを無視すると, ×ボタンから終了できなくなります.

SDL_Delay() は SDL1.2 と同じです. 指定した時間 (ミリ秒) だけ CPU を手放します. Windows でいう Sleep() と同じです (実際 Windows では Sleep() を呼び出します). このいわゆるスリープ処理は, 環境によっては 1 ミリ秒から有効となりますが, 一般的には 10 ミリ秒より短い時間だけスリープすることはできないと思った方がよいようです.

うん, この細かさじゃ書き続けられないな. 多分.

*1:特に深い理由はないけれど, C 言語は書き慣れていないので, サンプルは C++ 言語です. 細かい違いが面倒で……

*2:SDL 1.3 の r5524 までは SDL_WindowID というハンドルが使われていました. これに代わり新たに SDL_Window* を使うように変更されたので, この記事の内容もそれに合わせて修正しました.