[ ホーム | TSG について | 活動 / 分科会 | イベント | メンバー | メーリングリスト | 部報 | 掲示板 | リンク ]

SDL / OpenGL 分科会 - 第 2 回

はじめの一歩

とりあえず SDL を使った最小限のプログラムはこんな感じになります。

#include <SDL.h>
#include <stdio.h>

int main(int argc,char *argv[])
{
    /* 初期化 */
    if(SDL_Init(SDL_INIT_VIDEO)<0) {
        fprintf(stderr,"failed to initialize SDL.\n");
        return -1;
    }

    /* 終了 */
    SDL_Quit();
    return 0;
}

まあつまり初期化は SDL_Init() で終了は SDL_Quit() とゆーことです。 特に説明しなくても分かりそうですが、一応少し補足しておくと :

コンパイル

で、これのコンパイルですが、

$ gcc -o test test.c

ではダメで、

$ gcc -o test test.c `sdl-config --cflags --libs`

とします (` は左下がりではなく右下がりの引用符)。 この `sdl-config --cflags --libs` というのは SDL のヘッダファイルやライブラリの位置をコンパイラに教えるためのものです (これをつけないと SDL.h などが見つかりません)。 気になる人は

$ sdl-config --cflags --libs

としてみると良いでしょう。 ヘッダやライブラリの位置が出てくると思います。 コマンド名を ` で囲んでおくとその出力結果がコマンドラインに展開されるので、それがコンパイラに伝わるという仕組みです。

ドキュメントを読もう

ここまでで SDL_Init(), SDL_Quit() という 2 つの関数が出てきました。 この後もいろいろと出てきます。 しかしこれらを全部細かく説明しているとあまりにも長くなってしまうので、不明な点などがあれば各自 SDL のドキュメント (和訳あり) を参照して下さい。

あと、ちょっと調べたいときは UNIX の man コマンドでも調べられます。 たとえば :

$ man SDL_Init

ループとイベント処理

さすがに初期化と終了だけでは寂しすぎるので、もう一歩進みましょう。

#include <SDL.h>

int main(int argc,char *argv[])
{
    /* 初期化 */
    if(SDL_Init(SDL_INIT_VIDEO)<0) {
        fprintf(stderr,"failed to initialize SDL.\n");
        return -1;
    }

    /* ループ */
    while(1) {
        SDL_Event event;
        while(SDL_PollEvent(&event))
            if(event.type==SDL_QUIT)
                goto QUIT;
        /* ゲームならここで何か処理する */
    }

    /* 終了 */
QUIT:
    SDL_Quit();
    return 0;
}

ゲームだとまあ大抵ループがあるわけですが、この中に必ず入れなければならない処理があります。 それはイベント処理とゆーものです。

たとえば Windows などではウィンドウの右上の×ボタンをクリックするとプログラムが終了します。 これはどういう仕組みかというと、×ボタンがクリックされたとき、Windows からそのプログラムに「終了しろー」というメッセージが送られて、それを検知したプログラムが終了する、という流れになっています。 つまり OS 側からそのようなメッセージが送られてきていないかをプログラムの方でチェックしていなければいけないのです (これをやっていないと×ボタンをクリックしても反応がないので、プログラムが暴走しているように見えます)。

上のプログラムでは

SDL_Event event;
while(SDL_PollEvent(&event))
    if(event.type==SDL_QUIT)
        goto QUIT;

という部分でその処理を行っています。 SDL_PollEvent() で発生したイベントを受け取って、それが SDL_QUIT (つまり「終了しろ」) なら終了します。 なお、 SDL_PollEvent() は未処理のイベントがたまっている間は 1、イベントが全部消化されたら 0 を返してくるので、上のようにして while ループから抜け出すことができます。 OK?

2D グラフィックス入門

さて、ここまで地味なプログラムばかりでしたが、いよいよグラフィックスに入っていきます。

初期化

とりあえず初期化 (画面モードの設定 & ウィンドウの作成) はこんな感じです (もちろんこれは SDL_Init()SDL_Quit() の間に入れるのですが、ここでは省略しています。 完全なコードはこのファイルの最後にあります)。

SDL_Surface *screen;
screen=SDL_SetVideoMode(640,480,16,SDL_SWSURFACE);
if(!screen) {
    fprintf(stderr,"failed to set video mode to 640x480x16.\n");
    SDL_Quit();
    exit(1);
}

まず SDL_Surface というのがありますが、これはメモリ上のグラフィック領域一般 (「サーフェス」) を指します。 ディスプレイに表示されている画面も当然これに入るので、上のように SDL_Surface *screen; と宣言しているわけです。 ちなみに、ファイルから読み込んだ画像なども「メモリ上のグラフィック領域」になりますから、やはり SDL_Surface になります。

初期化は SDL_SetVideoMode() で行います。

SDL_Surface *SDL_SetVideoMode(int width,int height,int bpp,Uint32 flags);

widthheight はもちろんウィンドウのサイズ。 bpp というのは bits per pixel の略で、1 ドットあたり何ビット使うか、 つまり log2(色数) になります。 また flags はオプションをいろいろ指定するのですが、今のところは SDL_SWSURFACE だけでいいでしょう (これは画面領域をシステムメモリ上にとるという意味ですが、あまり気にする必要はありません)。 1 つ便利なオプションを挙げてみるとすれば SDL_FULLSCREEN でしょうか。 文字通りフルスクリーンになります。 これは SDL_SWSURFACE|SDL_FULLSCREEN というようにビット和をとって指定します。

BMP の読み込みと表示

では、用意した画面に BMP を描画してみます。

int draw_bmp(char *filename,SDL_Surface *screen,int x,int y)
{
    SDL_Surface *image;
    SDL_Rect dest;

    /* BMP を読み込む */
    image=SDL_LoadBMP(filename);
    if(!image)
        return -1;

    /* 画面に描画 */
    dest.x=x;
    dest.y=y;
    SDL_BlitSurface(image,NULL,screen,&dest);

    /* 描画した部分の表示を更新 */
    SDL_UpdateRect(screen,x,y,image->w,image->h);

    /* 画像用に確保した領域を解放 */
    SDL_FreeSurface(image);

    return 0;
}

画像の読み込み (SDL_LoadBMP()) と解放 (SDL_FreeSurface()) は特に問題ないと思います。 SDL_UpdateRect()screen のうち転送した部分を指定しているだけですね。

で、残りの SDL_BlitSurface() ですが、このような関数です :

int SDL_BlitSurface(SDL_Surface *src,SDL_Rect *srcrect,
                    SDL_Surface *dst,SDL_Rect *dstrect);

これはサーフェスを転送する関数です。 srcdst にはそれぞれ転送元 (source)・転送先 (destination) のサーフェスを指定し、srcrect, dstrect には各サーフェスのどの部分を転送するかを指定します。 srcrect, dstrectSDL_Rect という型をもっており、x, y, w (width), h (height) というメンバー変数があります。 つまり、src の点 (12,34) を左上頂点とする幅 10, 高さ 20 の矩形を dst の点 (56,78) へ転送したければ、

SDL_Rect srcrect,dstrect;

srcrect.x=12;
srcrect.y=34;
srcrect.w=10;
srcrect.h=20;

dstrect.x=56;
dstrect.y=78;

SDL_BlitSurface(src,&srcrect,dst,&dstrect);

などとするわけです (dstrectwh を指定する必要はありません)。 ちなみに src 全体を転送する場合は srcrectNULL で OK です。

まとめ

以上をまとめると、次のようなプログラムになります。 とりあえずウィンドウが開いて画像が表示できているはずです。 画像を表示する度にファイルから読み込んでいるという無駄がありますが、そこは暇だったら各自直してみて下さい。

#include <SDL.h>

int draw_bmp(char *filename,SDL_Surface *screen,int x,int y)
{
    SDL_Surface *image;
    SDL_Rect dest;

    /* BMP を読み込む */
    image=SDL_LoadBMP(filename);
    if(!image)
        return -1;

    /* 画面に描画 */
    dest.x=x;
    dest.y=y;
    SDL_BlitSurface(image,NULL,screen,&dest);

    /* 描画した部分の表示を更新 */
    SDL_UpdateRect(screen,x,y,image->w,image->h);

    /* 画像用に確保した領域を解放 */
    SDL_FreeSurface(image);

    return 0;
}

int main(int argc,char *argv[])
{
    SDL_Surface *screen;

    /* 初期化 */
    if(SDL_Init(SDL_INIT_VIDEO)<0) {
        fprintf(stderr,"failed to initialize SDL.\n");
        return -1;
    }

    /* 画面作成 */
    screen=SDL_SetVideoMode(640,480,16,SDL_SWSURFACE);
    if(!screen) {
        fprintf(stderr,"failed to set video mode to 640x480x16.\n");
        SDL_Quit();
        return -1;
    }

    /* ループ */
    while(1) {
        /* イベント処理 */
        SDL_Event event;
        while(SDL_PollEvent(&event))
            if(event.type==SDL_QUIT)
                goto QUIT;

        /* BMP 描画 */
        draw_bmp("test.bmp",screen,12,34);
    }

    /* 終了 */
QUIT:
    SDL_Quit();
    return 0;
}