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


12 主人公の向き変更

いつまでも前ばかり向いている主人公では嫌なので、横、後ろも向くように変えます。

これを実行するためには主人公の横向き画像x2と後ろ向き画像が新たに必要となります。この画像は別ファイルで用意する方法と、一つのファイルにまとめる方法があります。

別ファイルで用意した場合は、4方向の画像をそれぞれ読み込み、方向によって使い分けることになります。一つのファイルで用意した場合は、画像を切り取って使うことになります。

今回のプログラムでは、後者の方法を採用しました。

画像は各方向を横1列に並べたものとします。(32*4x32のサイズになります。)

各画像の方向の順番は方法定義列挙体の順に沿って、前、右、左、後ろという順番にしました。方向定義列挙体の順番は前回のプログラムと異なっていますが、順番に特に意味がないので特に気にする必要はありません。

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

/* ウインドウ設定 */
#define WINDOW_WIDTH 640
#define WINDOW_HEIGHT 480
#define BPP 32

/* キャラクタ設定 */
#define CHAR_WIDTH 32
#define CHAR_HEIGHT 32

/* マップ設定 */
#define MAP_WIDTH 20
#define MAP_HEIGHT 15

/* フォント設定 */
#define FONT_NAME "ipag-mona.ttf"
#define FONT_SIZE 24

/* メッセージウインドウ */
#define MESSAGE_WIN_LEFT 0		/* メッセージウインドウ左端 */
#define MESSAGE_WIN_TOP 400		/* メッセージウインドウ上端 */
#define MESSAGE_WIN_WIDTH (WINDOW_WIDTH)			/* メッセージウインドウ幅 */
#define MESSAGE_WIN_HEIGHT (WINDOW_HEIGHT - MESSAGE_WIN_TOP)	/* メッセージウインドウ高さ */

/* メッセージ */
#define MESSAGE_PADDING 5		/* メッセージパディング */
#define MESSAGE_LEFT (MESSAGE_PADDING + MESSAGE_WIN_LEFT)	/* メッセージ左端 */
#define MESSAGE_TOP (MESSAGE_PADDING + MESSAGE_WIN_TOP)		/* メッセージ上端 */

/* 操作性 */
#define MOVE_SPEED (SDL_DEFAULT_REPEAT_INTERVAL * 5)		/* 歩くスピード */

/* 共通フォント */
TTF_Font *font;

/* 色 */
const SDL_Color color_white = {0xff, 0xff, 0xff};

/* レイヤー構造体 */
typedef struct{
	SDL_Surface *surface;	/* サーフェス */
	int use;		/* 使用有無 */
	int visible;		/* 表示有無 */
	SDL_Rect src_rect;	/* コピー元矩形 */
	SDL_Rect dst_rect;	/* コピー先矩形 */
} type_layer;

/* キャラクタ構造体 */
typedef struct{
	SDL_Surface *image;	/* キャラクタイメージ 左から前、右、左、後ろ */
				/* 方向定義列挙体と連動させる */
	int direction;		/* 方向 */
	SDL_Rect src_rect;	/* コピー元矩形 */
	SDL_Rect dst_rect;	/* コピー先矩形 */
} type_character;

/* レイヤー番号列挙体 */
enum enm_layer{
	lyrBackground = 0,	/* 背景 */
	lyrObject,		/* オブジェクト */
	lyrCharacter,		/* キャラクタ(ダミー) */
	lyrMessageWindow,	/* メッセージウインドウ */
	lyrMessage,		/* メッセージ */
	lyrEnd			/* 「ダミー」 */
};

/* 方向定義列挙体 */
enum enm_direction{
	dirSouth = 0,		/* 南 */
	dirEast,		/* 東 */
	dirWest,		/* 西 */
	dirNorth,		/* 北 */
	dirNone			/* 方向なし */
};

/* グローバル変数 */
type_layer g_game_layers[lyrEnd];	/* レイヤー */
char g_map[MAP_HEIGHT][MAP_WIDTH];	/* マップ */
type_character g_mycharacter;		/* 主人公キャラクタ */

/* ここから関数 */

/* マップ座標からピクセル座標への変換 */
int map2pix(SDL_Rect *rect, int map_x, int map_y){
	rect->x = map_x * CHAR_WIDTH;
	rect->y = map_y * CHAR_HEIGHT;
	return 0;
}

/* マップ座標からピクセル座標のRectを取得する */
int getpos(SDL_Rect *from, SDL_Rect *to){
	map2pix(to, from->x, from->y);
	return 0;
}

/* レイヤーのsrc_rectのサイズをサーフェスサイズに指定する */
int update_layer_rect_size(type_layer *layers, int index){
	(layers + index)->src_rect.w = (layers + index)->surface->w;
	(layers + index)->src_rect.h = (layers + index)->surface->h;
	return 0;
}

/* SDL_Rectを0で初期化 */
int init_rect(SDL_Rect* rect){
	/* memset0でもよかったな */
	rect->x = 0;
	rect->y = 0;
	rect->w = 0;
	rect->h = 0;
	return 0;
}

/* レイヤーのsrc_rectとdst_rectを初期化 */
int init_layer_rect(type_layer* layer, int index){
	init_rect(&((layer + index)->src_rect));
	init_rect(&((layer + index)->dst_rect));
	return 0;
}

/* レイヤーの可視状態を設定する */
int set_layer_visible(type_layer *layers, int index, int flag){
	(layers + index)->visible = flag;
	return 0;
}

/* サーフェス破棄 */
int drop_surface(SDL_Surface *surface){
	SDL_FreeSurface(surface);
	return 0;
}

/* レイヤーの破棄 */
int drop_layer(type_layer *layer, int index){
	drop_surface((layer + index)->surface);
	(layer + index)->use = 0;
	set_layer_visible(layer, index, 0);
	return 0;
}

/* キャラクタを書く(方向付き) */
int draw_character(type_character *character, SDL_Surface *screen){
	// 方向により使う画像を分ける
	switch(character->direction){
	case dirSouth:
	case dirNorth:
	case dirEast:
	case dirWest:
		break;
	default:
		return -1;
		break;
	}

	character->src_rect.x = character->direction * CHAR_WIDTH;
	character->src_rect.y = 0;
	character->src_rect.w = CHAR_WIDTH;
	character->src_rect.h = CHAR_HEIGHT;

	SDL_BlitSurface(
		character->image,
		&character->src_rect,
		screen,
		&character->dst_rect
	);

	return 0;
}

/* レイヤーの描画 */
int update_display(){
	SDL_Surface *video_surface = SDL_GetVideoSurface();
	int iLoop;

	for(iLoop = 0; iLoop < lyrEnd; iLoop ++){
		if(g_game_layers[iLoop].visible == 1){
			if(iLoop == lyrCharacter){
				/* キャラクタレイヤー描画時にはキャラクター描画関数を呼ぶ */
				draw_character(&g_mycharacter, video_surface);
			}
			else {
				/* サーフェスの複写 */
				SDL_BlitSurface(
					g_game_layers[iLoop].surface,
					&g_game_layers[iLoop].src_rect,
					video_surface,
					&g_game_layers[iLoop].dst_rect
				);
			}
		}
	}

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

	return 0;
}

/* メッセージ表示 */
int show_message(const char* msg){
	SDL_Event event;
	int close_msg = 0;

	g_game_layers[lyrMessage].surface = TTF_RenderUTF8_Blended(font, msg, color_white);

	update_layer_rect_size(g_game_layers, lyrMessage);

	set_layer_visible(g_game_layers, lyrMessageWindow, 1);
	set_layer_visible(g_game_layers, lyrMessage, 1);

	SDL_EnableKeyRepeat(0, 0);

	/* 描画 */
	update_display();

	/* メッセージが閉じるまで、元のイベントループには戻らない */
	while(close_msg == 0){
		if(SDL_PollEvent(&event)){
			switch(event.type){
			case SDL_KEYDOWN:
				if(event.key.keysym.sym == SDLK_SPACE){
					close_msg = 1;
				}
				break;
			default:
				break;
			}
		}
		SDL_Delay(1);
	}
	set_layer_visible(g_game_layers, lyrMessageWindow, 0);
	set_layer_visible(g_game_layers, lyrMessage, 0);
	update_display();

	SDL_EnableKeyRepeat(MOVE_SPEED, MOVE_SPEED);

	drop_layer(g_game_layers, lyrMessage);

	return 0;
}

/* レイヤーの初期化 */
int init_layers(){

	int iLoop;

	/* 矩形初期化 */
	for(iLoop = 0; iLoop < lyrEnd; iLoop ++){
		init_layer_rect(g_game_layers, iLoop);
		g_game_layers[iLoop].use = 0;
	}

	/* バックグラウンド */
	g_game_layers[lyrBackground].surface =
		SDL_CreateRGBSurface(SDL_HWSURFACE, WINDOW_WIDTH, WINDOW_HEIGHT, 32, 0, 0, 0, 0);
	update_layer_rect_size(g_game_layers, lyrBackground);
	SDL_FillRect(
		g_game_layers[lyrBackground].surface,
		&g_game_layers[lyrBackground].src_rect,
		SDL_MapRGB(g_game_layers[lyrBackground].surface->format, 60, 60, 60)
	);
	g_game_layers[lyrBackground].use = 1;
	set_layer_visible(g_game_layers, lyrBackground, 1);

	/* 話しかける対象 */
	g_game_layers[lyrObject].surface = IMG_Load("char2.png");
	update_layer_rect_size(g_game_layers, lyrObject);
	g_game_layers[lyrObject].use = 1;
	set_layer_visible(g_game_layers, lyrObject, 1);

	/* キャラクタ(カーソルキーで動かすやつ) */
	g_mycharacter.image = IMG_Load("char1.png");
	set_layer_visible(g_game_layers, lyrCharacter, 1);

	/* メッセージウインドウ */
	g_game_layers[lyrMessageWindow].surface = IMG_Load("msg_window.png");
	update_layer_rect_size(g_game_layers, lyrMessageWindow);
	set_layer_visible(g_game_layers, lyrMessageWindow, 0);
	g_game_layers[lyrMessageWindow].dst_rect.x = MESSAGE_WIN_LEFT;
	g_game_layers[lyrMessageWindow].use = 1;
	g_game_layers[lyrMessageWindow].dst_rect.y = MESSAGE_WIN_TOP;

	/* メッセージ */
	set_layer_visible(g_game_layers, lyrMessage, 0);
	g_game_layers[lyrMessage].dst_rect.x = MESSAGE_LEFT;
	g_game_layers[lyrMessage].dst_rect.y = MESSAGE_TOP;

	return 0;
}

/* 全レイヤーの破棄 */
int drop_layers(){
	int iLoop;
	for(iLoop = 0; iLoop < lyrEnd; iLoop ++){
		if(g_game_layers[iLoop].use == 1){
			drop_layer(g_game_layers, iLoop);
		}
	}
	return 0;
}	

/* プレイヤーの向きのオブジェクトがあるかどうかのチェック */
int check_object(int cur_x, int cur_y, int dir){
	switch(dir){
	case dirNorth:
		cur_y--;
		break;
	case dirEast:
		cur_x++;
		break;
	case dirSouth:
		cur_y++;
		break;
	case dirWest:
		cur_x--;
		break;
	case dirNone:
	default:
		break;
	}
	if(
		cur_x < 0 ||
		cur_x >= MAP_WIDTH ||
		cur_y < 0 ||
		cur_y >= MAP_HEIGHT
	){
		return -1;
	}
	else {
		return g_map[cur_x][cur_y];
	}

	return -2;
}

/* メイン関数 */
int main(int argc, char* argv[]){
	SDL_Surface* image;
	SDL_Rect scr_rect, rect_tmp;
	SDL_Event event;
	int exit_prg = 0;
	int iRet;
	g_mycharacter.direction = dirSouth;

	SDL_Init(SDL_INIT_EVERYTHING);

	SDL_EnableKeyRepeat(MOVE_SPEED, MOVE_SPEED);

	TTF_Init();

	SDL_SetVideoMode(WINDOW_WIDTH, WINDOW_HEIGHT, BPP, SDL_HWSURFACE);

	/* フォント読み込み */
	font = TTF_OpenFont(FONT_NAME, FONT_SIZE);

	/* 画像読み込み */
	init_layers();

	/* マップ構成 */
	memset(g_map, 0x00, sizeof(g_map));
	g_map[1][1] = 1;
	map2pix(&g_game_layers[lyrObject].dst_rect, 1, 1);

	/* 画像配置位置情報の設定 */
	init_rect(&scr_rect);

	/* 描画 */
	update_display();

	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 -= 1;
					g_mycharacter.direction = dirNorth;
					break;
				case SDLK_DOWN:
					rect_tmp.y += 1;
					g_mycharacter.direction = dirSouth;
					break;
				case SDLK_RIGHT:
					rect_tmp.x += 1;
					g_mycharacter.direction = dirEast;
					break;
				case SDLK_LEFT:
					rect_tmp.x -= 1;
					g_mycharacter.direction = dirWest;
					break;
				case SDLK_ESCAPE:
					exit_prg = 1;
					break;
				case SDLK_SPACE:
					/* 話す/調べる */
					iRet = check_object(
						scr_rect.x,
						scr_rect.y,
						g_mycharacter.direction
					);
					if(iRet < 1){
						show_message("そこにはなにもありません。");
					}
					else {
						show_message("おっさん「何か用か?」");
					}
					break;
				default:
					break;
				}

				/* 移動可能範囲の判定 */
				if(check_object(rect_tmp.x, rect_tmp.y, dirNone) == 0){
					scr_rect = rect_tmp;
					/* 描画 */
					getpos(&scr_rect, &g_mycharacter.dst_rect);
					update_display();
				}
				else {
					rect_tmp = scr_rect;
					update_display();
				}
				break;
			default:
				break;
			}
		}
		SDL_Delay(1);
	}

	drop_layers();

	drop_surface(g_mycharacter.image);

	TTF_CloseFont(font);

	TTF_Quit();

	SDL_Quit();

	return 0;
}

キャラクタの描画処理をまったく別の処理に回し、レイヤー描画関数内で、キャラクタレイヤーの番になったらキャラクタ描画関数を呼ぶという仕組みになっています。キャラクタレイヤーはダミーレイヤーとなっています。未使用だけれども(use = 0)、表示はする(visible = 1)という特殊な形になっています。

キャラクタ画像切り抜きはdirectionがそのまま順番になっていますので、等間隔に切り抜いて張り付けます。

↓実行例

ピクセル画像を用意しないといけないのが面倒ですね。こういう実行例画像を用意するといかにも4方向用意したという感じがしますが、実は、前と左右逆転で簡単に作れる後ろしか用意していなかったりして。

おっさんは上向いたりしませんが、同じように画像を用意して、処理を少し変えればできます。これはまた別の機会にしましょう。


/

indexに戻る