SDLのサンプル集

部内のイベント用に作成したSDLのサンプルです。 ここに置いてあるコードおよび画像は自由に使ってもらってかまいません。

テンプレ

初期化処理と終了処理、メインループなどを実装したテンプレートです。 background.pngという画像が(VC8の場合,このファイルと同じディレクトリに)あることを仮定しています。 用意するのが面倒な場合は、下に画像をおいておいたのでそれを使って試してみてください。

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


SDL_Surface* screen = NULL;                 /* 画面 */
SDL_Surface* background_image = NULL;       /* 背景画像 */


/* 更新する */
void Update(void)
{
    /* TODO: ここにゲームの状態を更新する処理を書く */
}


/* 描画する */
void Draw(void)
{
    /* 背景を描画する */
    SDL_BlitSurface(background_image, NULL, screen, NULL);

    /* TODO: ここに描画処理を書く */

    /* 画面を更新する */
    SDL_UpdateRect(screen, 0, 0, 0, 0);
}


/* 初期化する。
 * 成功したときは0を、失敗したときは-1を返す。
 */
int Initialize(void)
{
    /* SDLを初期化する */
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) < 0) {
        fprintf(stderr, "SDLの初期化に失敗しました:%s\n", SDL_GetError());
        return -1;
    }
    /* 画面のタイトルを変更する */
    SDL_WM_SetCaption("My SDL Sample Game", NULL);
    /* 画面を初期化する */
    screen = SDL_SetVideoMode(640, 480, 32, SDL_SWSURFACE);
    if (screen == NULL) {
        fprintf(stderr, "画面の初期化に失敗しました:%s\n", SDL_GetError());
        SDL_Quit();
        return -1;
    }

    /* TODO: ここで画像や音声を読み込む */
    background_image = IMG_Load("background.png");
    if (background_image == NULL) {
        fprintf(stderr, "画像の読み込みに失敗しました:%s\n", SDL_GetError());
        SDL_Quit();
        return -1;
    }

    return 0;
}


/* メインループ */
void MainLoop(void)
{
    SDL_Event event;
    double next_frame = SDL_GetTicks();
    double wait = 1000.0 / 60;

    for (;;) {
        /* すべてのイベントを処理する */
        while (SDL_PollEvent(&event)) {
            /* QUIT イベントが発生するか、ESC キーが押されたら終了する */
            if ((event.type == SDL_QUIT) ||
                (event.type == SDL_KEYUP && event.key.keysym.sym == SDLK_ESCAPE))
                return;
        }
        /* 1秒間に60回Updateされるようにする */
        if (SDL_GetTicks() >= next_frame) {
            Update();
            /* 時間がまだあるときはDrawする */
            if (SDL_GetTicks() < next_frame + wait)
                Draw();
            next_frame += wait;
            SDL_Delay(0);
        }
    }
}


/* 終了処理を行う */
void Finalize(void)
{
    /* TODO: ここで画像や音声を解放する */
    SDL_FreeSurface(background_image);

    /* 終了する */
    SDL_Quit();
}


/* メイン関数 */
int main(int argc, char* argv[])
{
    if (Initialize() < 0)
        return -1;
    MainLoop();
    Finalize();
    return 0;
}

大量のオブジェクトを扱うサンプル

大量のオブジェクトをアニメーションさせながら動かしてみるサンプル。 ポインタは使わない方針(SDL_Surface*は除く)。

#include <SDL.h>
#include <SDL_image.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>


#define MAX_PARTICLE_COUNT 512


SDL_Surface* screen = NULL;                 /* 画面 */
SDL_Surface* background_image = NULL;       /* 背景画像 */
SDL_Surface* particle_image = NULL;         /* 粒子の画像 */


/* 粒子 */
typedef struct {
    double x, y;        /* 座標 */
    double vx, vy;      /* 速度 */
    int alive;          /* 0: 死亡, 1: 生存 */
    int type;           /* 0: 小さい粒子, 1: 大きい粒子 */
    double frame;       /* アニメーションは今何フレーム目か */
} Particle;

/* 粒子の配列 */
Particle particles[MAX_PARTICLE_COUNT];


/* lower 以上 upper 以下の乱数を返す */
double Random(double lower, double upper)
{
   return ((double)rand() / RAND_MAX) * (upper - lower) + lower;
}


/* 粒子を作る。
 * 実際には粒子の配列から死んでる粒子を一個見つけてきて
 * それを生き返らせて、パラメーターを設定する。
 */
int CreateParticle(double x, double y, double vx, double vy, int type)
{
    int i;

    /* 死んでる粒子を探す */
    for (i = 0; i < MAX_PARTICLE_COUNT; ++i) {
        if (!particles[i].alive)
            break;
    }
    if (i < MAX_PARTICLE_COUNT) {   /* 見つかった */
        particles[i].alive = 1;
        particles[i].x = x;
        particles[i].y = y;
        particles[i].vx = vx;
        particles[i].vy = vy;
        particles[i].type = type;
        particles[i].frame = 0;
        return i;
    } else {                        /* 見つからなかった */
        return -1;
    }
}


/* index番目の粒子を更新する */
void UpdateParticle(int index)
{
    particles[index].vy += 0.4;
    particles[index].x += particles[index].vx;
    particles[index].y += particles[index].vy;

    particles[index].frame += 0.5;
    if (particles[index].frame >= 4)
        particles[index].frame = 0;

    /* 画面下に出ていたら死亡 */
    if (particles[index].y > 480)
        particles[index].alive = 0;
}


/* 更新する */
void Update(void)
{
    int i;

    /* 生きている全粒子を更新する */
    for (i = 0; i < MAX_PARTICLE_COUNT; ++i) {
        if (particles[i].alive)
            UpdateParticle(i);
    }

    CreateParticle(320, 240, Random(-5, 5), Random(-8, -16), 0);
    CreateParticle(312, 232, Random(-5, 5), Random(-8, -16), 1);
}


/* 粒子を描画する */
void DrawParticle(int index)
{
    SDL_Rect srcrect;
    SDL_Rect destrect = { particles[index].x, particles[index].y };

    switch (particles[index].type) {
    case 0:
        srcrect.x = (int)particles[index].frame * 16;
        srcrect.y = 0;
        srcrect.w = srcrect.h = 16;
        break;
    case 1:
        srcrect.x = (int)particles[index].frame * 32;
        srcrect.y = 16;
        srcrect.w = srcrect.h = 32;
        break;
    }

    SDL_BlitSurface(particle_image, &srcrect, screen, &destrect);
}


/* 描画する */
void Draw(void)
{
    int i;

    /* 背景を描画する */
    SDL_BlitSurface(background_image, NULL, screen, NULL);

    /* 生きている全粒子を描画する */
    for (i = 0; i < MAX_PARTICLE_COUNT; ++i) {
        if (particles[i].alive)
            DrawParticle(i);
    }

    /* 画面を更新する */
    SDL_UpdateRect(screen, 0, 0, 0, 0);
}


/* 初期化する。
 * 成功したときは0を、失敗したときは-1を返す。
 */
int Initialize(void)
{
    int i;

    /* SDLを初期化する */
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) < 0) {
        fprintf(stderr, "SDLの初期化に失敗しました:%s\n", SDL_GetError());
        return -1;
    }
    /* 画面のタイトルを変更する */
    SDL_WM_SetCaption("My SDL Sample Game", NULL);
    /* 画面を初期化する */
    screen = SDL_SetVideoMode(640, 480, 32, SDL_SWSURFACE);
    if (screen == NULL) {
        fprintf(stderr, "画面の初期化に失敗しました:%s\n", SDL_GetError());
        SDL_Quit();
        return -1;
    }

    /* 画像を読み込む */
    background_image = IMG_Load("background2.png");
    if (background_image == NULL) {
        fprintf(stderr, "画像の読み込みに失敗しました:%s\n", SDL_GetError());
        SDL_Quit();
        return -1;
    }
    particle_image = IMG_Load("particle.png");
    if (particle_image == NULL) {
        fprintf(stderr, "画像の読み込みに失敗しました:%s\n", SDL_GetError());
        SDL_Quit();
        return -1;
    }

    /* 乱数を初期化する */
    srand(time(NULL));

    /* 粒子の配列を初期化する */
    for (i = 0; i < MAX_PARTICLE_COUNT; ++i)
        particles[i].alive = 0;

    return 0;
}


/* メインループ */
void MainLoop(void)
{
    SDL_Event event;
    double next_frame = SDL_GetTicks();
    double wait = 1000.0 / 60;

    for (;;) {
        /* すべてのイベントを処理する */
        while (SDL_PollEvent(&event)) {
            /* QUIT イベントが発生するか、ESC キーが押されたら終了する */
            if ((event.type == SDL_QUIT) ||
                (event.type == SDL_KEYUP && event.key.keysym.sym == SDLK_ESCAPE))
                return;
        }
        /* 1秒間に60回Updateされるようにする */
        if (SDL_GetTicks() >= next_frame) {
            Update();
            /* 時間がまだあるときはDrawする */
            if (SDL_GetTicks() < next_frame + wait)
                Draw();
            next_frame += wait;
            SDL_Delay(0);
        }
    }
}


/* 終了処理を行う */
void Finalize(void)
{
    SDL_FreeSurface(background_image);
    SDL_FreeSurface(particle_image);

    /* 終了する */
    SDL_Quit();
}


/* メイン関数 */
int main(int argc, char* argv[])
{
    if (Initialize() < 0)
        return -1;
    MainLoop();
    Finalize();
    return 0;
}

テキスト表示のサンプル

アルファベットを羅列した画像を読み込んで、テキストの表示を行うサンプルです。 DrawTextは汎用的に作ったのでコピペ推奨(stdarg.hおよびctype.hのインクルードを忘れずに)。 background.pngはテンプレのものを使用してください。

#include <SDL.h>
#include <SDL_image.h>
#include <stdio.h>
#include <stdarg.h>
#include <ctype.h>


SDL_Surface* screen = NULL;                 /* 画面 */
SDL_Surface* background_image = NULL;       /* 背景画像 */
SDL_Surface* letters_image = NULL;          /* 文字画像 */

int counter = 0;                            /* 毎フレーム1づつカウントアップするカウンタ */


/* (x, y)にテキスト(ただし、数字、アルファベット、空白のみ)を表示する。
 * printf と同じような書き方ができる。
 */
void DrawText(int x, int y, const char* format, ...)
{
    int i;
    va_list ap;
    char buffer[256];

    va_start(ap, format);
    vsnprintf(buffer, 255, format, ap);
    va_end(ap);
    buffer[255] = '\0';
    for (i = 0; buffer[i] != '\0'; ++i) {
        SDL_Rect srcrect1, srcrect2;
        SDL_Rect destrect1 = { x + i*10, y };
        SDL_Rect destrect2 = { x + i*10 + 2, y + 2 };

        srcrect1.w = srcrect1.h = srcrect2.w = srcrect2.h = 10;
        if (isdigit(buffer[i])) {           /* 数字 */
            srcrect1.x = (buffer[i] - '0') * 10;
            srcrect1.y = 20;
        } else if (isalpha(buffer[i])) {    /* アルファベット */
            srcrect1.x = (toupper(buffer[i]) - 'A') * 10;
            srcrect1.y = 0;
        } else {                            /* それ以外は空白とみなす */
            continue;
        }
        srcrect2.x = srcrect1.x;
        srcrect2.y = srcrect1.y + 10;
        SDL_BlitSurface(letters_image, &srcrect2, screen, &destrect2);
        SDL_BlitSurface(letters_image, &srcrect1, screen, &destrect1);
    }
}


/* 更新する */
void Update(void)
{
    ++counter;
    if (counter == 60 * 10) {              /* 10秒経ったら終了イベントを発生させる */
        SDL_Event quit_event = { SDL_QUIT };
        SDL_PushEvent(&quit_event);
    }
}


/* 描画する */
void Draw(void)
{
    /* 背景を描画する */
    SDL_BlitSurface(background_image, NULL, screen, NULL);

    /* 文字を60フレームの周期で点滅させて表示する */
    if (counter / 30 % 2 == 0) {
        DrawText(200, 235, "PRESS ESC KEY TO EXIT");
    }
    /* カウントダウンを表示する */
    DrawText(270, 255, "%2d SEC", 10 - counter / 60);

    /* 画面を更新する */
    SDL_UpdateRect(screen, 0, 0, 0, 0);
}


/* 初期化する。
 * 成功したときは0を、失敗したときは-1を返す。
 */
int Initialize(void)
{
    /* SDLを初期化する */
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) < 0) {
        fprintf(stderr, "SDLの初期化に失敗しました:%s\n", SDL_GetError());
        return -1;
    }
    /* 画面のタイトルを変更する */
    SDL_WM_SetCaption("My SDL Sample Game", NULL);
    /* 画面を初期化する */
    screen = SDL_SetVideoMode(640, 480, 32, SDL_SWSURFACE);
    if (screen == NULL) {
        fprintf(stderr, "画面の初期化に失敗しました:%s\n", SDL_GetError());
        SDL_Quit();
        return -1;
    }

    /* 画像を読み込む */
    background_image = IMG_Load("background.png");
    if (background_image == NULL) {
        fprintf(stderr, "画像の読み込みに失敗しました:%s\n", SDL_GetError());
        SDL_Quit();
        return -1;
    }
    letters_image = IMG_Load("letters.png");
    if (letters_image == NULL) {
        fprintf(stderr, "画像の読み込みに失敗しました:%s\n", SDL_GetError());
        SDL_Quit();
        return -1;
    }

    return 0;
}


/* メインループ */
void MainLoop(void)
{
    SDL_Event event;
    double next_frame = SDL_GetTicks();
    double wait = 1000.0 / 60;

    for (;;) {
        /* すべてのイベントを処理する */
        while (SDL_PollEvent(&event)) {
            /* QUIT イベントが発生するか、ESC キーが押されたら終了する */
            if ((event.type == SDL_QUIT) ||
                (event.type == SDL_KEYUP && event.key.keysym.sym == SDLK_ESCAPE))
                return;
        }
        /* 1秒間に60回Updateされるようにする */
        if (SDL_GetTicks() >= next_frame) {
            Update();
            /* 時間がまだあるときはDrawする */
            if (SDL_GetTicks() < next_frame + wait)
                Draw();
            next_frame += wait;
            SDL_Delay(0);
        }
    }
}


/* 終了処理を行う */
void Finalize(void)
{
    SDL_FreeSurface(background_image);
    SDL_FreeSurface(letters_image);

    /* 終了する */
    SDL_Quit();
}


/* メイン関数 */
int main(int argc, char* argv[])
{
    if (Initialize() < 0)
        return -1;
    MainLoop();
    Finalize();
    return 0;
}

キー入力処理のサンプル

SDL_GetKeyState関数を呼び出せば、その瞬間のキーの状態を取得することができます。 これだけで十分な場合もありますが、エッジ(ボタンを押した瞬間)を検出したい場合や、複数のキーを同一視したい場合([↓]でも[J]でも下方向に移動させたいとか)、もう少し複雑な仕組みが必要です。

このサンプルではいったんInputData構造体に入力データを保存することで、上の二つを実現しています。

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


/* 入力データ */
typedef struct {
    int left;
    int up;
    int right;
    int down;
    int button1;
    int button2;
} InputData;


InputData current_input, prev_input;        /* 入力データ */

SDL_Surface* screen = NULL;                 /* 画面 */
SDL_Surface* background_image = NULL;       /* 背景画像 */
SDL_Surface* onpu_image = NULL;             /* 音符 */

double x, y;                                /* 音符の座標 */


/* 入力データを更新する */
void UpdateInput(void)
{
    Uint8* keys = SDL_GetKeyState(NULL);

    prev_input = current_input;
    current_input.left = keys[SDLK_LEFT] | keys[SDLK_h];        /* [←], [H] */
    current_input.up = keys[SDLK_UP] | keys[SDLK_k];            /* [↑], [K] */
    current_input.right = keys[SDLK_RIGHT] | keys[SDLK_l];      /* [→], [L] */
    current_input.down = keys[SDLK_DOWN] | keys[SDLK_j];        /* [↓], [J] */
    current_input.button1 = keys[SDLK_LSHIFT] | keys[SDLK_z];   /* 左Shift, [Z] */
    current_input.button2 = keys[SDLK_LCTRL] | keys[SDLK_x];    /* 左Ctrl, [X] */
}


/* 更新する */
void Update(void)
{
    int speed = 6;

    UpdateInput();

    /* ボタン1が押されているときは低速 */
    if (current_input.button1)
        speed = 2;
    /* ボタン2が押されたら(0, 0)に移動 */
    if (current_input.button2 && !prev_input.button2)   /* 押された瞬間のみ */
        x = y = 0;

    /* 入力にしたがって移動する */
    if (current_input.left)
        x -= speed;
    if (current_input.right)
        x += speed;
    if (current_input.up)
        y -= speed;
    if (current_input.down)
        y += speed;

    /* 画面外に出ないようにする */
    if (x < 0)
        x = 0;
    else if (x > 640 - 64)
        x = 640 - 64;
    if (y < 0)
        y = 0;
    else if (y > 480 - 64)
        y = 480 - 64;
}


/* 描画する */
void Draw(void)
{
    SDL_Rect destrect = { x, y };

    /* 背景を描画する */
    SDL_BlitSurface(background_image, NULL, screen, NULL);

    /* 音符を描画する */
    SDL_BlitSurface(onpu_image, NULL, screen, &destrect);

    /* 画面を更新する */
    SDL_UpdateRect(screen, 0, 0, 0, 0);
}


/* 初期化する。
 * 成功したときは0を、失敗したときは-1を返す。
 */
int Initialize(void)
{
    /* SDLを初期化する */
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) < 0) {
        fprintf(stderr, "SDLの初期化に失敗しました:%s\n", SDL_GetError());
        return -1;
    }
    /* 画面のタイトルを変更する */
    SDL_WM_SetCaption("My SDL Sample Game", NULL);
    /* 画面を初期化する */
    screen = SDL_SetVideoMode(640, 480, 32, SDL_SWSURFACE);
    if (screen == NULL) {
        fprintf(stderr, "画面の初期化に失敗しました:%s\n", SDL_GetError());
        SDL_Quit();
        return -1;
    }

    /* 画像を読み込む */
    background_image = IMG_Load("background.png");
    if (background_image == NULL) {
        fprintf(stderr, "画像の読み込みに失敗しました:%s\n", SDL_GetError());
        SDL_Quit();
        return -1;
    }
    onpu_image = IMG_Load("onpu.png");
    if (onpu_image == NULL) {
        fprintf(stderr, "画像の読み込みに失敗しました:%s\n", SDL_GetError());
        SDL_Quit();
        return -1;
    }

    return 0;
}


/* メインループ */
void MainLoop(void)
{
    SDL_Event event;
    double next_frame = SDL_GetTicks();
    double wait = 1000.0 / 60;

    for (;;) {
        /* すべてのイベントを処理する */
        while (SDL_PollEvent(&event)) {
            /* QUIT イベントが発生するか、ESC キーが押されたら終了する */
            if ((event.type == SDL_QUIT) ||
                (event.type == SDL_KEYUP && event.key.keysym.sym == SDLK_ESCAPE))
                return;
        }
        /* 1秒間に60回Updateされるようにする */
        if (SDL_GetTicks() >= next_frame) {
            Update();
            /* 時間がまだあるときはDrawする */
            if (SDL_GetTicks() < next_frame + wait)
                Draw();
            next_frame += wait;
            SDL_Delay(0);
        }
    }
}


/* 終了処理を行う */
void Finalize(void)
{
    SDL_FreeSurface(background_image);
    SDL_FreeSurface(onpu_image);

    /* 終了する */
    SDL_Quit();
}


/* メイン関数 */
int main(int argc, char* argv[])
{
    if (Initialize() < 0)
        return -1;
    MainLoop();
    Finalize();
    return 0;
}

コード片

マウスの座標を取得する

int mouse_x, mouse_y;
SDL_GetMouseState(&mouse_x, &mouse_y);

フルスクリーンにする

SDL_SetVideoMode関数の第4引数にSDL_FULLSCREENの指定を加える。

/* 画面を初期化する */
screen = SDL_SetVideoMode(640, 480, 32, SDL_SWSURFACE | SDL_FULLSCREEN);

トップに戻る