SDLは「Simple DirectMedia Layer」の略称です。
オーディオ、キーボード、マウス、ジョイスティック、
OpenGLによる3Dハードウェアと2Dビデオフレームバッファへの
低レベルアクセスを提供するように設計された、
クロスプラットフォームマルチメディアライブラリです。
クロスプラットフォームですので、様々な環境で動作させることが可能です。
グラフィックを扱うには、SDL_image、
サウンドを扱うには、SDL_mixer、
ttfフォントを扱うには、SDL_ttf、
など、各種便利なライブラリが存在します。
開発・動作環境は、様々な環境で整えることができます。
自分はwindowsとlinuxの環境を使用しています。
windowsでは、コンパイラに「BCC」を使用し、
開発環境に「BCC Developer」を使用しています。
自分はwindowsXPですが、windows7でも動作するのを確認しています。
linuxでは、OSに「Fedora12」を使用しています。
こちらは「VirtualBox」にて仮想環境で走らせることが多いです。
仮想環境を使用した場合、若干サウンドの再生に遅延が発生するようです。
また、ジョイスティックの認識にも、ひと手間かかります。
グラフィックの表示を行うプログラムの流れは以下になります。
以下、プログラム例です。
#include "SDL/SDL.h" #include "SDL/SDL_image.h" #define FALSE 0 #define TRUE 1 #define SCRW 320 #define SCRH 240 SDL_Event event; void input(int *flag); int main(int argc, char* argv[]){ int flag; SDL_Surface *bg; SDL_Surface *screen; //SDLの初期化 if((SDL_Init( SDL_INIT_VIDEO ) == -1)){ fprintf(stderr, "SDLを初期化できませんでした: %s\n",SDL_GetError()); exit(1); } //ウィンドウサーフェスの生成 screen = SDL_SetVideoMode(SCRW, SCRH, 32, SDL_SWSURFACE); if(screen == NULL){ fprintf(stderr, "ビデオモード設定できませんでした: %s\n",SDL_GetError()); exit(1); } //絵のデータを読み込んで、そのアドレスを取得 bg = IMG_Load("img/bg.png"); flag = TRUE; while(flag == TRUE){ input(&flag); //ウィンドウサーフェスへの描画 SDL_BlitSurface(bg, NULL, screen, NULL); //ウィンドウサーフェスの更新 SDL_UpdateRect(screen, 0, 0, 0, 0); //10msのウェイト SDL_Delay(10); } SDL_FreeSurface(bg); SDL_Quit(); return 0; } void input(int *flag){ //イベントキューからイベントを取得 while(SDL_PollEvent(&event)){ switch( event.type ){ //キーが押された case SDL_KEYDOWN: switch(event.key.keysym.sym){ //押されたキーはESC case SDLK_ESCAPE: *flag = FALSE; break; } break; //ウィンドウの×ボタンが押された case SDL_QUIT: *flag = FALSE; break; } } }
このプログラムを実行すると、ウィンドウの中に絵が表示されます。
また、入力イベントを受け取ることで、プログラムが終了するようになっています。
サウンドの再生を行うには、以下の手順が必要です。
以下、プログラム例になります。
#include "SDL/SDL.h" #include "SDL/SDL_image.h" #include "SDL/SDL_mixer.h" #define FALSE 0 #define TRUE 1 #define SCRW 320 #define SCRH 240 SDL_Event event; void input(int *flag); int main(int argc, char* argv[]){ int flag; SDL_Surface *bg; SDL_Surface *screen; Mix_Chunk *sound; //SDLの初期化 if((SDL_Init( SDL_INIT_VIDEO | SDL_INIT_AUDIO ) == -1)){ fprintf(stderr, "SDLを初期化できませんでした: %s\n", SDL_GetError()); exit(1); } //オーディオの初期化 if(Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 1024) < 0){ fprintf(stderr,"SDL_mixerを初期化できませんでした: %s\n", SDL_GetError()); exit(1); } //ウィンドウサーフェスの初期化 screen = SDL_SetVideoMode(SCRW, SCRH, 32, SDL_SWSURFACE); if(screen == NULL){ fprintf(stderr, "ビデオモードに設定できませんでした: %s\n",SDL_GetError()); exit(1); } //絵のデータを読み込んで、そのアドレスを取得 bg = IMG_Load("img/bg.png"); //音のデータを読み込んで、そのアドレスを取得 sound = Mix_LoadWAV("sound/sound.wav"); //音の再生 Mix_PlayChannel(0, sound, -1); flag = TRUE; while(flag == TRUE){ input(&flag); //ウィンドウサーフェスへの描画 SDL_BlitSurface(bg, NULL, screen, NULL); //ウィンドウサーフェスの更新 SDL_UpdateRect(screen, 0, 0, 0, 0); //10msウエイト SDL_Delay(10); } SDL_FreeSurface(bg); Mix_FreeChunk(sound); Mix_CloseAudio(); SDL_Quit(); return 0; } void input(int *flag){ while(SDL_PollEvent(&event)){ switch( event.type ){ case SDL_KEYDOWN: switch(event.key.keysym.sym){ case SDLK_ESCAPE: *flag = FALSE; break; } break; case SDL_QUIT: *flag = FALSE; break; } } }
このプログラムを実行すると、ウィンドウの中に絵が表示され、音がループ再生されます。
また、入力イベントを受け取ることで、プログラムが終了するようになっています。
再生を行っているMix_PlayChannel関数は、次のような仕様になっています。
第1引数 : チャンネル
第2引数 : サウンドデータのアドレス
第3引数 : 再生回数
再生回数は、
-1 : ループ再生
0 : 1回再生
1 : 2回再生
...
となっています。
ここでは、「複数のコマが描かれた画像」を使用した方法を解説します。
SDL_BlitSurface関数では、「転送元画像内の特定の部分」をウインドウサーフェスに描画することができます。
その際に利用するのが、SDL_Rect構造体です。
typedef struct{ Sint16 x, y; Uint16 w, h; } SDL_Rect;
下のような画像の場合、
SDL_Surface *kamome = IMG_Load("kamome.png"); SDL_Rect src = {0, 0, 48, 48}; SDL_Rect dst = {100, 200}; SDL_BlitSurface(kamome, &src, screen, &dst);
と書くと、画像の以下の部分をウィンドウの(100, 200)座標に描画することになります。
src.x及び、src.yは転送元画像内の矩形の左上座標で、
src.w及び、src.hは転送元画像内の矩形の幅と高さです。
よって、横にコマを並べた画像の場合、
「src.xをsrc.wずつ加算」してあげることでアニメーションさせることが可能です。
実際のプログラムでは、構造体を利用してキャラクタのアニメーションを実現します。
以下、プログラム例になります。
#include "SDL/SDL.h" #include "SDL/SDL_image.h" #define FALSE 0 #define TRUE 1 #define SCRW 320 #define SCRH 240 typedef struct { int count; int frame; int frameNum; int interval; } Anime; typedef struct { SDL_Surface *img; SDL_Rect src, dst; Anime anime; } Graphic; SDL_Event event; void input(int *flag); int animation(Graphic *g); int main(int argc, char* argv[]){ int flag; SDL_Surface *bg; Graphic kamome; SDL_Surface *screen; //ビデオとオーディオ、それからデフォルトのサブシステムを初期化 if((SDL_Init( SDL_INIT_VIDEO ) == -1)){ fprintf(stderr, "SDLを初期化できませんでした: %s\n", SDL_GetError()); exit(1); } //ソフトウェアサーフェスを要求し、画面を SCRW x SCRH で初期化 screen = SDL_SetVideoMode(SCRW, SCRH, 32, SDL_SWSURFACE); if(screen == NULL){ fprintf(stderr, "ビデオモードに設定できませんでした: %s\n",SDL_GetError()); exit(1); } //絵のデータを読み込んで、そのアドレスを取得 bg = IMG_Load("img/bg.png"); //キャラクタの初期化 { Graphic init = { NULL, //img {0,0,48,48}, //src {100,100,0,0}, //dst {0,0,4,8}}; //anime kamome = init; } kamome.img = IMG_Load("img/kamome.png"); flag = TRUE; while(flag == TRUE){ input(&flag); animation(&kamome); SDL_BlitSurface(bg, NULL, screen, NULL); SDL_BlitSurface(kamome.img, &(kamome.src), screen, &(kamome.dst)); SDL_UpdateRect(screen, 0, 0, 0, 0); SDL_Delay(10); } SDL_FreeSurface(bg); SDL_FreeSurface(kamome.img); SDL_Quit(); return 0; } void input(int *flag){ while(SDL_PollEvent(&event)){ switch( event.type ){ case SDL_KEYDOWN: switch(event.key.keysym.sym){ case SDLK_ESCAPE: *flag = FALSE; break; } break; case SDL_QUIT: *flag = FALSE; break; } } } int animation(Graphic *g){ g->anime.count++; if(g->anime.count > g->anime.interval){ g->anime.count = 0; if(g->anime.frame < g->anime.frameNum - 1){ g->src.x += g->src.w; g->anime.frame++; } else{ g->src.x = 0; g->anime.frame = 0; return 1; } } return 0; }
キャラクタ同士の当たり判定の取り方は、ゲームによっても様々あると思いますが、
ここでは典型的な矩形(四角のこと)同士の当たり判定を考えます。
まず、「player」と「enemy」がいたと仮定して考えてみましょう。
便宜上、各キャラクタの左上X座標を「x」、Y座標を「y」、幅を「w」、高さを「h」とし、
「player.x」、「enemy.w」などと表記することにします。
矩形同士の判定を行うには、4つの条件のANDを取ります。
つまり、4つの条件全てを満たした時に「2つの矩形が重なった」と判定できるのです。
(1) enemyの右端ラインが、playerの左端ラインよりも大きい
(図1)
(2) enemyの左端ラインが、playerの右端ラインよりも小さい
(図2)
(3) enemyの下端ラインが、playerの上端ラインよりも大きい
(図3)
(4) enemyの上端ラインが、playerの下端ラインよりも小さい
(図4)
以上4つの条件を全て満たす時、2つの矩形が重なっていることになります。
具体的に条件式で表すと、以下のようになります。
if(enemy.x + enemy.w > player.x && enemy.x < player.x + player.w && enemy.y + enemy.h > player.y && enemy.y < player.y + player.h){ //ここに判定時の処理を記述します }
画面が徐々に明るくなったり、暗くなったりする演出です。
具体的には以下のようなやり方で実現しています。
透明度は、SDL_SetAlpha関数によって変更可能です。
[255]が透明ではない状態で、
[ 0 ]が完全に透明な状態になります。
以下、プログラム例になります。
#include "SDL/SDL.h" #include "SDL/SDL_image.h" #define FALSE 0 #define TRUE 1 #define SCRW 320 #define SCRH 240 typedef struct { int count; int frame; int frameNum; int interval; int alpha; } Anime; typedef struct { SDL_Surface *img; SDL_Rect src, dst; Anime anime; } Graphic; typedef struct { Graphic graphic; int speed; } Fade; void fadeInInit(Fade *fade, int speed); int fadeIn(Fade *fade); void fadeOutInit(Fade *fade, int speed); int fadeOut(Fade *fade); SDL_Event event; int main(int argc, char* argv[]){ SDL_Surface *screen, *bg; Fade fade; enum sequenceID{ FADE_IN, GAME_PLAY, FADE_OUT, QUIT }; enum sequenceID seq; //SDLの初期化 if((SDL_Init( SDL_INIT_VIDEO ) == -1)){ fprintf(stderr, "SDLを初期化できませんでした: %s\n", SDL_GetError()); exit(1); } //ウィンドウサーフェスの初期化 screen = SDL_SetVideoMode(SCRW, SCRH, 32, SDL_SWSURFACE); if(screen == NULL){ fprintf(stderr, "ビデオモードに設定できませんでした: %s\n",SDL_GetError()); exit(1); } //絵のデータを読み込んで、そのアドレスを取得 bg = IMG_Load("img/bg.png"); fade.graphic.img = IMG_Load("img/black.png"); //フェードインの初期設定 fadeInInit(&fade, 8); seq = FADE_IN; while(seq != QUIT){ switch(seq){ case FADE_IN: //フェードイン if(fadeIn(&fade)){ seq = GAME_PLAY; } break; case GAME_PLAY: //入力イベント処理 if(input() == TRUE){ //フェードアウトの初期設定 fadeOutInit(&fade, 8); seq = FADE_OUT; } break; case FADE_OUT: //フェードアウト if(fadeOut(&fade)){ seq = QUIT; } break; } //ウィンドウサーフェスへの描画 SDL_BlitSurface(bg, NULL, screen, NULL); SDL_BlitSurface(fade.graphic.img, NULL, screen, NULL); //ウィンドウサーフェスの更新 SDL_UpdateRect(screen, 0, 0, 0, 0); //10msウエイト SDL_Delay(10); } //読み込んだ画像の開放 SDL_FreeSurface(bg); SDL_FreeSurface(fade.graphic.img); SDL_Quit(); return 0; } int input(){ int returnValue = FALSE; while(SDL_PollEvent(&event)){ switch( event.type ){ case SDL_KEYDOWN: case SDL_QUIT: returnValue = TRUE; break; } } return returnValue; } //------------------------------------- //フェード用変数をフェードイン用に初期化 //------------------------------------- void fadeInInit(Fade *fade, int speed){ fade->speed = speed; fade->graphic.anime.alpha = 255; SDL_SetAlpha(fade->graphic.img, SDL_SRCALPHA, fade->graphic.anime.alpha); } //------------------------------------- //フェードイン(フェードが完了すると1を返す) //------------------------------------- int fadeIn(Fade *fade){ fade->graphic.anime.alpha -= fade->speed; if(fade->graphic.anime.alpha < 0){ fade->graphic.anime.alpha = 0; } SDL_SetAlpha(fade->graphic.img, SDL_SRCALPHA, fade->graphic.anime.alpha); if(fade->graphic.anime.alpha == 0){ return 1; } else{ return 0; } } //------------------------------------- //フェード用変数をフェードアウト用に初期化 //------------------------------------- void fadeOutInit(Fade *fade, int speed){ fade->speed = speed; fade->graphic.anime.alpha = 0; SDL_SetAlpha(fade->graphic.img, SDL_SRCALPHA, fade->graphic.anime.alpha); } //------------------------------------- //フェードアウト(フェードが完了すると1を返す) //------------------------------------- int fadeOut(Fade *fade){ fade->graphic.anime.alpha += fade->speed; if(fade->graphic.anime.alpha > 255){ fade->graphic.anime.alpha = 255; } SDL_SetAlpha(fade->graphic.img, SDL_SRCALPHA, fade->graphic.anime.alpha); if(fade->graphic.anime.alpha == 255){ return 1; } else{ return 0; } }
このプログラムを実行すると、フェードインしながら画像が表示され、
キー入力によって、フェードアウトしてウィンドウが閉じます。
ゲーム全体の流れを管理し、切り替えていくために、
流れを管理するための変数を用意します。
その変数の値によって、様々な処理に分岐するようにします。
以下、プログラム例になります。
#include "SDL/SDL.h" #include "SDL/SDL_image.h" #include "title.h" #include "mainGame.h" #define SCRW 320 #define SCRH 240 enum mainSequenceID{ TITLE, MAIN_GAME, END }; SDL_Surface *screen; //------------------------------------- //メイン //------------------------------------- int main(int argc, char* argv[]){ //全体の流れを管理する変数 int seqFlag; //SDLの初期化 if((SDL_Init( SDL_INIT_VIDEO | SDL_INIT_AUDIO ) == -1)){ fprintf(stderr, "SDLを初期化できませんでした: %s\n", SDL_GetError()); exit(1); } //ウィンドウサーフェスを生成 screen = SDL_SetVideoMode(SCRW, SCRH, 32, SDL_SWSURFACE ); if(screen == NULL){ fprintf(stderr, "ビデオモードに設定できませんでした: %s\n",SDL_GetError()); exit(1); } seqFlag = TITLE; while(seqFlag != END){ switch(seqFlag){ //タイトル画面の処理 case TITLE: title(&seqFlag); break; //メインゲームの処理 case MAIN_GAME: mainGame(&seqFlag); break; } } SDL_Quit(); return 0; }
このプログラムでは、seqFlagという変数によって、
タイトル画面やメインゲームに遷移させています。
1つのソースにいろんな関数を作っていくと、ソース自体も長くなり、編集が大変になってきます。
そこで、分かりやすいまとまりでファイルを分割します。
例えば、ゲームのプログラミングでは、「入力」「キャラクタ」「描画」などのまとまりで分割しています。
cソースファイルと同時に、「関数のプロトタイプ宣言」を記述したヘッダファイルを作成しておきます。
他のファイルから関数を利用したいときは、このヘッダを読み込ませます。
このヘッダファイルを読み込むことで、他のファイルでも関数を使用できるようになります。