トップページに戻る / GCCページに戻る / SDL目次


6 キーで画像を動かす

いよいよ、キー操作で、画像を動かすということをやります。これができると、一気にゲームっぽくなりますね。

とりあえず、ソースコード全体を書きます。

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

#define WINDOW_WIDTH 640
#define WINDOW_HEIGHT 480
#define BPP 32

int draw(SDL_Surface *image, SDL_Rect *rect1, SDL_Rect *rect2);

int main(int argc, char* argv[]){
	SDL_Surface* image;
	SDL_Rect rect, scr_rect, rect_tmp;
	SDL_Event event;
	int exit_prg = 0;

	SDL_Init(SDL_INIT_EVERYTHING);

	SDL_SetVideoMode(WINDOW_WIDTH, WINDOW_HEIGHT, BPP, SDL_HWSURFACE);

	/* 画像読み込み */
	image = IMG_Load("sample.png");

	/* 画像の矩形情報設定 */
	rect.x = 0;
	rect.y = 0;
	rect.w = image->w;
	rect.h = image->h;

	/* 画像配置位置情報の設定 */
	scr_rect.x = 0;
	scr_rect.y = 0;

	/* 描画 */
	draw(image, &rect, &scr_rect);

	rect_tmp = scr_rect;

	/* イベントループ */
	while(exit_prg == 0){	
		if(SDL_PollEvent(&event)){
			switch(event.type){
			case SDL_KEYDOWN:
				switch(event.key.keysym.sym){
				case SDLK_UP:
					rect_tmp.y -= rect.h;
					break;
				case SDLK_DOWN:
					rect_tmp.y += rect.h;
					break;
				case SDLK_RIGHT:
					rect_tmp.x += rect.w;
					break;
				case SDLK_LEFT:
					rect_tmp.x -= rect.w;
					break;
				case SDLK_ESCAPE:
					exit_prg = 1;
					break;
				default:
					break;
				}

				/* 移動可能範囲の判定 */
				if(rect_tmp.x >= 0 && rect_tmp.x + rect.w <= WINDOW_WIDTH && rect_tmp.y >= 0 && rect_tmp.y + rect.h <= WINDOW_HEIGHT){
					scr_rect = rect_tmp;
					/* 描画 */
					draw(image, &rect, &scr_rect);
				}
				else {
					rect_tmp = scr_rect;
				}
				break;
			default:
				break;
			}
		}
		SDL_Delay(1);
	}

	SDL_FreeSurface(image);

	SDL_Quit();

	return 0;
}

int draw(SDL_Surface *image, SDL_Rect *rect1, SDL_Rect *rect2){
	SDL_Rect window_rect = {0, 0, WINDOW_WIDTH, WINDOW_HEIGHT};
	SDL_Surface *video_surface;
	
	video_surface = SDL_GetVideoSurface();

	/* 背景色塗りつぶし */
	SDL_FillRect(video_surface, &window_rect, SDL_MapRGB(video_surface->format, 0, 0, 0));

	/* サーフェスの複写 */
	SDL_BlitSurface(image, rect1, video_surface, rect2);

	/* サーフェスフリップ */
	SDL_Flip(video_surface);

	return 0;
}

今回のソースコードは長いので、分割して解説します。


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

#define WINDOW_WIDTH 640
#define WINDOW_HEIGHT 480
#define BPP 32

int draw(SDL_Surface *image, SDL_Rect *rect1, SDL_Rect *rect2);

main関数直前まで。SDL_imageを用います。ビットマップイメージの読み込みであれば、SDL_imageは不要です。ウインドウの幅、高さはミスを防ぐためにWINDOW_WIDTH、WINDOW_HEIGHTと定義しておきました。また、描画用関数として、draw()を作成しました。


int main(int argc, char* argv[]){
	SDL_Surface* image;
	SDL_Rect rect, scr_rect, rect_tmp;
	SDL_Event event;
	int exit_prg = 0;

	SDL_Init(SDL_INIT_EVERYTHING);

	SDL_SetVideoMode(WINDOW_WIDTH, WINDOW_HEIGHT, BPP, SDL_HWSURFACE);

rect_tmpサーフェスを追加。これは移動可能範囲判定に使用します。画面外に飛び出さないようにするためです。

SDL_Eventはイベント、例えばキーが押された、マウスクリックされたなどのイベントをトラップするために用意しておきます。今回は、キーが押されたかどうかによって画像を動かします。

SDL_Eventは共用体で、以下のような様々なイベントをトラップできます。

typedef union SDL_Event {
	Uint8 type;
	SDL_ActiveEvent active;
	SDL_KeyboardEvent key;
	SDL_MouseMotionEvent motion;
	SDL_MouseButtonEvent button;
	SDL_JoyAxisEvent jaxis;
	SDL_JoyBallEvent jball;
	SDL_JoyHatEvent jhat;
	SDL_JoyButtonEvent jbutton;
	SDL_ResizeEvent resize;
	SDL_ExposeEvent expose;
	SDL_QuitEvent quit;
	SDL_UserEvent user;
	SDL_SysWMEvent syswm;
} SDL_Event;

イベントの種類はtypeに格納されており、以下の値が使用されます。

typedef enum SDL_Events {
       SDL_NOEVENT = 0,			/* Unused (do not remove) */
       SDL_ACTIVEEVENT,			/* Application loses/gains visibility */
       SDL_KEYDOWN,			/* Keys pressed */
       SDL_KEYUP,			/* Keys released */
       SDL_MOUSEMOTION,			/* Mouse moved */
       SDL_MOUSEBUTTONDOWN,		/* Mouse button pressed */
       SDL_MOUSEBUTTONUP,		/* Mouse button released */
       SDL_JOYAXISMOTION,		/* Joystick axis motion */
       SDL_JOYBALLMOTION,		/* Joystick trackball motion */
       SDL_JOYHATMOTION,		/* Joystick hat position change */
       SDL_JOYBUTTONDOWN,		/* Joystick button pressed */
       SDL_JOYBUTTONUP,			/* Joystick button released */
       SDL_QUIT,			/* User-requested quit */
       SDL_SYSWMEVENT,			/* System specific event */
       SDL_EVENT_RESERVEDA,		/* Reserved for future use.. */
       SDL_EVENT_RESERVEDB,		/* Reserved for future use.. */
       SDL_VIDEORESIZE,			/* User resized video mode */
       SDL_VIDEOEXPOSE,			/* Screen needs to be redrawn */
       SDL_EVENT_RESERVED2,		/* Reserved for future use.. */
       SDL_EVENT_RESERVED3,		/* Reserved for future use.. */
       SDL_EVENT_RESERVED4,		/* Reserved for future use.. */
       SDL_EVENT_RESERVED5,		/* Reserved for future use.. */
       SDL_EVENT_RESERVED6,		/* Reserved for future use.. */
       SDL_EVENT_RESERVED7,		/* Reserved for future use.. */
       /* Events SDL_USEREVENT through SDL_MAXEVENTS-1 are for your use */
       SDL_USEREVENT = 24,
       /* This last event is only for bounding internal arrays
	  It is the number of bits in the event mask datatype -- Uint32
        */
       SDL_NUMEVENTS = 32
} SDL_EventType;

SDL_SetVideoMode()では最初に定義した値を用いるようにします。


	/* 画像読み込み */
	image = IMG_Load("sample.png");

	/* 画像の矩形情報設定 */
	rect.x = 0;
	rect.y = 0;
	rect.w = image->w;
	rect.h = image->h;

	/* 画像配置位置情報の設定 */
	scr_rect.x = 0;
	scr_rect.y = 0;

	/* 描画 */
	draw(image, &rect, &scr_rect);

	rect_tmp = scr_rect;

初期描画部分です。SDL_imageを使用しない場合は、SDL_LoadBMP()でビットマップファイルを読み込みます。draw()の中身は後述します。


そして、いよいよかんじんなイベントループ部分です */

	/* イベントループ */
	while(exit_prg == 0){	
		if(SDL_PollEvent(&event)){
			switch(event.type){
			case SDL_KEYDOWN:
				switch(event.key.keysym.sym){
				case SDLK_UP:
					rect_tmp.y -= rect.h;
					break;
				case SDLK_DOWN:
					rect_tmp.y += rect.h;
					break;
				case SDLK_RIGHT:
					rect_tmp.x += rect.w;
					break;
				case SDLK_LEFT:
					rect_tmp.x -= rect.w;
					break;
				case SDLK_ESCAPE:
					exit_prg = 1;
					break;
				default:
					break;
				}

				/* 移動可能範囲の判定 */
				if(rect_tmp.x >= 0 && rect_tmp.x + rect.w <= WINDOW_WIDTH && rect_tmp.y >= 0 && rect_tmp.y + rect.h <= WINDOW_HEIGHT){
					scr_rect = rect_tmp;
					/* 描画 */
					draw(image, &rect, &scr_rect);
				}
				else {
					rect_tmp = scr_rect;
				}
				break;
			default:
				break;
			}
		}
		SDL_Delay(1);
	}

終了フラグ(exit_prg)が0以外になるまでループが回りつづけます。

ループの中でイベントのチェックを行います。チェックの手順は以下のとおりです。

  1. イベントがあるか (SDL_PollEvent)
  2. イベントの種類チェック
  3. 押されたキーのチェック

SDL_PollEvent()

int SDL_PollEvent(SDL_Event *event);

イベントを取得します。イベントがあれば1が返り、引数にイベント情報が格納されます。イベントがなければ、0が返ります。


イベントの種類チェックはSDL_Event共用体のtypeをチェックします。

typeがSDL_KEYDOWNであれば、キーが押されたイベントが発生しているので、さらに、どのキーが押されたのかチェックします。

その情報は、SDL_keyboardEventのkeysymのsymになります。

typedef struct SDL_KeyboardEvent {
	Uint8 type;	/* SDL_KEYDOWN or SDL_KEYUP */
	Uint8 which;	/* The keyboard device index */
	Uint8 state;	/* SDL_PRESSED or SDL_RELEASED */
	SDL_keysym keysym;
} SDL_KeyboardEvent;
typedef struct SDL_keysym {
	Uint8 scancode;			/* hardware specific scancode */
	SDLKey sym;			/* SDL virtual keysym */
	SDLMod mod;			/* current key modifiers */
	Uint16 unicode;			/* translated character */
} SDL_keysym;

SDLKeyの種類は多いので、wikiを参考にしてください。

この情報から、上が押されたら座標を-方向に、下が押されたら+方向に移動させるなどrect_tmpを編集します。

その後、移動可能範囲のチェックを行い、範囲内であればrect_tmpをscr_rectにコピーし、移動の確定、描画を行います。範囲外であれば、scr_rectをrect_tmpにコピーし、移動を取り消します。

ループの最後にSDL_Delay(1)がありますが、これがないと個のプログラムに対し、CPUがフルパワーで回転するのでリソースの無駄遣いになります。

なお、ESCキーが押されたら、exit_prg=1となり、ループを抜けます。



	SDL_FreeSurface(image);

	SDL_Quit();

	return 0;
}

終了処理です。


さて、drawの中身ですが、

int draw(SDL_Surface *image, SDL_Rect *rect1, SDL_Rect *rect2){
	SDL_Rect window_rect = {0, 0, WINDOW_WIDTH, WINDOW_HEIGHT};
	SDL_Surface *video_surface;
	
	video_surface = SDL_GetVideoSurface();

	/* 背景色塗りつぶし */
	SDL_FillRect(video_surface, &window_rect, SDL_MapRGB(video_surface->format, 0, 0, 0));

	/* サーフェスの複写 */
	SDL_BlitSurface(image, rect1, video_surface, rect2);

	/* サーフェスフリップ */
	SDL_Flip(video_surface);

	return 0;
}

で、背景色の塗りつぶしと描画の処理を行っています。


SDL_FillRect()

int SDL_FillRect(SDL_Surface *dst, SDL_Rect *dstrect, Uint32 color);

塗りつぶしをします。dstは塗りつぶすサーフェス、dstrectは塗りつぶし範囲、colorは色で、SDL_MapRGB()の返り値をセットします。


もし、背景色ではなく、背景画像を用いる場合は、背景画像、キーで動かす画像の順にSDL_BlitSurface()で描きます。


これで、カーソルキーで画像を動かすことができるようになりました。あと、数字キー(テンキー)で動くようにしたり、斜め移動を搭載したりなど色々いじってみてください。

なお、キー押しっぱなしで動くようにするには、SDL_EnableKeyRepeat()を使います。

int SDL_EnableKeyRepeat(int delay, int interval);

delayはリピート前までにかかる時間をミリ秒で、intervalはリピート間隔をミリ秒で指定します。delayを0にすると、リピートしなくなります。デフォルト値として、SDL_DEFAULT_REPEAT_DELAYとSDL_DEFAULT_REPEAT_INTERVALが用意されています

キャラクタを動かすのであれば、delayとintervalを同じ値にすると自然になります。


/

indexに戻る