部内のイベント用に作成した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);