SDLとは

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」にて仮想環境で走らせることが多いです。
仮想環境を使用した場合、若干サウンドの再生に遅延が発生するようです。
また、ジョイスティックの認識にも、ひと手間かかります。

グラフィックの表示

グラフィックの表示を行うプログラムの流れは以下になります。

  • SDLの初期化
  • ウィンドウサーフェスの生成
  • 絵のデータを読み込む
  • ウィンドウサーフェスへの描画
  • ウィンドウサーフェスの更新

以下、プログラム例です。

#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)

enemyの右端ラインは、「enemyのX座標 + 幅」
playerの左端ラインは、「playerのX座標」
になるので、条件式で表すと以下のようになります。

enemy.x + enemy.w > player.x

(2) enemyの左端ラインが、playerの右端ラインよりも小さい (図2)

enemyの左端ラインは、「enemyのX座標」
playerの右端ラインは、「playerのX座標 + 幅」
になるので、条件式で表すと以下のようになります。

enemy.x < player.x + player.w

(3) enemyの下端ラインが、playerの上端ラインよりも大きい (図3)

enemyの下端ラインは、「enemyのY座標 + 高さ」
playerの上端ラインは、「playerのY座標」
になるので、条件式で表すと以下のようになります。

enemy.y + enemy.h > player.y

(4) enemyの上端ラインが、playerの下端ラインよりも小さい (図4)

enemyの上端ラインは、「enemyのY座標」
playerの下端ラインは、「playerのY座標 + 高さ」
になるので、条件式で表すと以下のようになります。

enemy.y < player.y + player.h

以上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ソースファイルと同時に、「関数のプロトタイプ宣言」を記述したヘッダファイルを作成しておきます。
他のファイルから関数を利用したいときは、このヘッダを読み込ませます。
このヘッダファイルを読み込むことで、他のファイルでも関数を使用できるようになります。


▲ page top




Home Page Top