テキストの貼り付けはSDL_ttfを使います。ただ、UTF-8を使っている都合でVisual Studioを使っている場合はソースにべた書きするのが難しいかもしれないので、簡単なファイル入出力をするようにしました。
SDLではSDL_RWops構造体を使ってファイルの入出力を行います。
標準入出力 | SDL | 説明 |
---|---|---|
fopen() | SDL_RWFromFile() | ファイルオープン |
fclose() | SDL_RWClose() | ファイルクローズ |
fread() | SDL_RWread() | ファイル読み込み |
fwrite() | SDL_RWwrite() | ファイル書き込み |
ftell() | SDL_RWtell() | 現在地取得 |
fseek() | SDL_RWseek() | ファイルシーク |
なお、標準入出力ではファイルポインタ(FILE*)を使ってファイル操作をしますが、SDLではSDL_RWops構造体を使います。構造体の中身はSDL_rwops.hを確認してください。
SDL_RWops *SDL_RWFromFile(const char *file, const char *mode);
ファイルオープンです。fopen()とほぼ変わりません。*modeもfopen()とおなじく、「w」だの「r」だの指定します。
また、ファイル名ではなく、ファイルポインタを指定する方法もあります。
SDL_RWops *SDL_RWFromFP(FILE *fp, int autoclose);
この場合、「ファイルを開く」ではなく、「開かれたファイルからSDL_RWops構造体を作成する」という方が適当かもしれません。
autocloseは0以外を指定すると、SDL_RWopsを閉じたときに(SDL_RWClose())自動的にファイルポインタもクローズします。
int (SDLCALL *close)(struct SDL_RWops *context); #define SDL_RWclose(ctx) (ctx)->close(ctx)
SDL_RWopsのクローズをします。SDL_PWops構造体のメンバの関数ポインタcloseを呼び出します。
ctxにはクローズするSDL_RWops構造体のポインタを指定します。
成功時に0、失敗時に-1が返ります。
int (SDLCALL *read)(struct SDL_RWops *context, void *ptr, int size, int maxnum); #define SDL_RWread(ctx, ptr, size, n) (ctx)->read(ctx, ptr, size, n)
SDL_PWops構造体のメンバの関数ポインタreadを呼び出します。
ctxにはSDL_RWops構造体のポインタを、ptrには読み込みバッファ領域のポインタを、sizeには読み込み単位を、nには読み込み数を指定します。(バイト数としてはptr*nバイトの読み込みが行われます。
返り値は実際に読み込んだ数が返ります。失敗したら-1が返ります。
int (SDLCALL *write)(struct SDL_RWops *context, const void *ptr, int size, int num); #define SDL_RWwrite(ctx, ptr, size, n) (ctx)->write(ctx, ptr, size, n)
SDL_PWops構造体のメンバの関数ポインタwriteを呼び出します。
ctxにはSDL_RWops構造体のポインタを、ptrには書き込みバッファ領域のポインタを、sizeには書き込み単位を、nには書き込み数を指定します。(バイト数としてはptr*nバイトの書き込みが行われます。
返り値は実際に書き込んだ数が返ります。失敗したら-1が返ります。
int (SDLCALL *seek)(struct SDL_RWops *context, int offset, int whence); #define SDL_RWtell(ctx) (ctx)->seek(ctx, 0, SEEK_CUR) #define SDL_RWseek(ctx, offset, whence) (ctx)->seek(ctx, offset, whence)
SDL_PWops構造体のメンバの関数ポインタseekを呼び出します。
ctxにはSDL_RWops構造体のポインタを、offsetには基準値(whence)からのオフセットを指定します。
whenceにはRW_SEEK_SET(先頭)、RW_SEEK_CUR(現在地)、RW_SEEK_END(末尾)のいずれかがセットされます。
返り値は先頭からのオフセットが返されます。
以上、関数の説明ですが、標準入出力を使ってはいけないというわけではないので、何を使うかは作り手の判断に任せられます。
以下のプログラムでは改行区切りのテキストを行単位で読み込んでメモリに格納するということをやっています。必要なテキストファイルは後述します。
#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 MSG_WIN_BORDER 5 #define MSG_WIN_PADDING (MSG_WIN_BORDER + 4) #define MSG_WIN_COLOR(__x) SDL_MapRGB(__x, 0x00, 0x00, 0xAA) #define MSG_WIN_BORDER_COLOR(__x) SDL_MapRGB(__x, 0x00, 0x00, 0x66) #define MSG_WIN_OPACITY 160 #define BATTLE_COMMAND_WIN_X 0 #define BATTLE_COMMAND_WIN_Y 300 #define BATTLE_COMMAND_WIN_W 180 #define BATTLE_COMMAND_WIN_H (WINDOW_HEIGHT - BATTLE_COMMAND_WIN_Y) #define BATTLE_STATUS_WIN_X (BATTLE_COMMAND_WIN_W + 1) #define BATTLE_STATUS_WIN_Y (BATTLE_COMMAND_WIN_Y) #define BATTLE_STATUS_WIN_W (WINDOW_WIDTH - BATTLE_STATUS_WIN_X) #define BATTLE_STATUS_WIN_H (WINDOW_HEIGHT - BATTLE_STATUS_WIN_Y) #define BATTLE_TOP_WIN_X 0 #define BATTLE_TOP_WIN_Y 0 #define BATTLE_TOP_WIN_W WINDOW_WIDTH #define BATTLE_TOP_WIN_H 40 /* フォント設定 */ #define FONT_NAME "ipag-mona.ttf" #define FONT_SIZE 24 /* データファイル */ #define TEXT_COMMAND "command.txt" /* コマンド */ #define TEXT_NAME "name.txt" /* 名前 */ #define TEXT_COMMON "battle_common.txt" /* 戦闘共通 */ /* テキストサイズ(UTF-8なので、サイズ=文字数とならないことに注意) */ #define BYTES_NAME 24 #define BYTES_COMMAND 24 #define BYTES_BATTLE_COMMON 80 /* 色 */ const SDL_Color color_black = {0x00, 0x00, 0x00}; const SDL_Color color_white = {0xFF, 0xFF, 0xFF}; const SDL_Color color_red = {0xFF, 0x00, 0x00}; const SDL_Color color_yellow = {0xFF, 0xFF, 0x00}; /* レイヤー構造体 */ typedef struct{ SDL_Surface *surface; /* サーフェス */ int use; /* 使用有無 */ int visible; /* 表示有無 */ SDL_Rect src_rect; /* コピー元矩形 */ SDL_Rect dst_rect; /* コピー先矩形 */ } type_layer; typedef struct{ SDL_Surface *image; SDL_Rect src_rect; SDL_Rect dst_rect; } type_enemy_image; /* ステータス */ typedef struct{ int hp; int mhp; int mp; int mmp; } type_status; /* レイヤー番号列挙体 */ enum enm_layer{ lyrBackground = 0, /* 背景 */ lyrEnemy, /* 敵 */ lyrStatuswin_1, /* ステータスウインドウ 1 */ lyrStatustxt_1, /* ステータステキスト 1 */ lyrCommandwin_1, /* コマンドウインドウ 1 */ lyrCommandtxt_1, /* コマンドテキスト 1*/ lyrTopwin, /* 上段ウインドウ */ lyrEnd /* 「ダミー」 */ }; /* コマンド番号列挙体 */ enum enm_command{ cmdAttack = 0, /* 戦う */ cmdMagic, /* 魔法 */ cmdDefend, /* 防御 */ cmdItem, /* アイテム */ cmdEscape, /* 逃げる */ cmdEnd /* 「ダミー」 */ }; /* 共通テキスト番号列挙体 */ enum enm_battle_common{ btlcmnStatus = 0, /* ステータス */ btlcmnEnd }; /* グローバル変数 */ type_enemy_image g_enemy_image; type_layer g_game_layers[lyrEnd]; /* レイヤー */ int g_iDisplayUpdateFlag; /* 画面アップデートフラグ */ char g_achCommands[cmdEnd][BYTES_COMMAND]; /* コマンド */ char g_achCharacterNames[1][BYTES_NAME]; /* キャラクタ名 */ char g_achCommonText[btlcmnEnd][BYTES_BATTLE_COMMON]; /* 戦闘共通テキスト */ type_status g_my_status; /* 自分のステータス */ TTF_Font *font; /* 共通フォント */ /***************************************************************** * ここから関数 * *****************************************************************/ /***************************************************************** * 更新フラグ関係 * *****************************************************************/ /* 更新フラグセット */ int set_update_display_flag(){ g_iDisplayUpdateFlag = 1; return 0; } /* 更新フラグリセット */ int reset_update_display_flag(){ g_iDisplayUpdateFlag = 0; return 0; } /* 更新フラグ取得 */ int get_update_display_flag(){ return g_iDisplayUpdateFlag; } /***************************************************************** * レイヤー関係 * *****************************************************************/ /* レイヤーの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; } /* レイヤー配置位置を設定する */ int update_layer_rect_pos(type_layer *layers, int index, int x, int y){ (layers + index)->dst_rect.x = x; (layers + index)->dst_rect.y = y; 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 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; } /***************************************************************** * ファイル入出力(基本) * *****************************************************************/ /* ファイルオープン */ SDL_RWops* file_open(const char *filename, const char *mode){ return SDL_RWFromFile(filename, mode); } SDL_RWops* file_open_for_read_text(const char *filename){ return file_open(filename, "r"); } SDL_RWops* file_open_for_read_binary(const char *filename){ return file_open(filename, "rb"); } SDL_RWops* file_open_for_write_binary(const char *filename){ return file_open(filename, "wb"); } /* ファイルクローズ */ int file_close(SDL_RWops* ctx){ return SDL_RWclose(ctx); } /* ファイル書き込み */ int file_write(SDL_RWops* ctx, void* ptr, int size, int num){ return SDL_RWwrite(ctx, ptr, size, num); } /* ファイル読み込み */ int file_read(SDL_RWops* ctx, void* ptr, int size, int num){ return SDL_RWread(ctx, ptr, size, num); } /* ファイルシーク */ int file_seek(SDL_RWops* ctx, int offset, int whence){ return SDL_RWseek(ctx, offset, whence); } /***************************************************************** * ファイル入出力(応用) * * UTF-8を想定 * *****************************************************************/ /* 1バイト読み込み */ char file_read_byte(SDL_RWops* ctx){ char buf; file_read(ctx, &buf, 1, 1); return buf; } /* 1バイト書き込み */ int file_write_byte(SDL_RWops* ctx, char buf){ return file_write(ctx, &buf, 1, 1); } /* 1行読み込み */ int file_read_line(SDL_RWops* ctx, char* buf, int maxsize){ int loop; for(loop = 0; loop < maxsize; loop ++){ *buf = file_read_byte(ctx); if(*buf == 0x0A || *buf == EOF){ *buf = 0x00; break; } buf++; } return 0; } /***************************************************************** * テキスト描画 * *****************************************************************/ /* コマンド */ int draw_command_text( type_layer *layers, int window_index, int text_index, SDL_Surface *video_surface ){ int iLoop; for(iLoop = 0; iLoop < cmdEnd; iLoop++){ update_layer_rect_pos( layers, text_index, (layers + window_index)->dst_rect.x + MSG_WIN_PADDING, (layers + window_index)->dst_rect.y + MSG_WIN_PADDING + iLoop * (FONT_SIZE + MSG_WIN_PADDING) ); (layers + text_index)->surface = TTF_RenderUTF8_Blended(font, g_achCommands[iLoop], color_white); update_layer_rect_size(layers, text_index); SDL_BlitSurface( (layers + text_index)->surface, &(layers + text_index)->src_rect, video_surface, &(layers + text_index)->dst_rect ); drop_surface((layers + text_index)->surface); } return 0; } /* ステータス */ int draw_status_text( type_layer *layers, int window_index, int text_index, SDL_Surface *video_surface ){ int iLoop; char achText[256]; for(iLoop = 0; iLoop < 1; iLoop++){ update_layer_rect_pos( layers, text_index, (layers + window_index)->dst_rect.x + MSG_WIN_PADDING, (layers + window_index)->dst_rect.y + MSG_WIN_PADDING + iLoop * (FONT_SIZE + MSG_WIN_PADDING) ); sprintf(achText, g_achCommonText[btlcmnStatus], g_achCharacterNames[0], g_my_status.hp, g_my_status.mhp, g_my_status.mp, g_my_status.mmp ); (layers + text_index)->surface = TTF_RenderUTF8_Blended(font, achText, color_white); update_layer_rect_size(layers, text_index); SDL_BlitSurface( (layers + text_index)->surface, &(layers + text_index)->src_rect, video_surface, &(layers + text_index)->dst_rect ); drop_surface((layers + text_index)->surface); } return 0; } /***************************************************************** * タイマー関数 * *****************************************************************/ /* アニメーション */ Uint32 enemy_animation(Uint32 interval, void* param){ type_enemy_image *enimage = (type_enemy_image*)param; enimage->src_rect.x = enimage->src_rect.w - enimage->src_rect.x; set_update_display_flag(); return interval; } /* レイヤーの描画 */ Uint32 update_display(Uint32 interval, void* param){ SDL_Surface *video_surface = SDL_GetVideoSurface(); int iLoop; /* アップデートフラグのチェック */ if(get_update_display_flag() == 0){ return interval; } for(iLoop = 0; iLoop < lyrEnd; iLoop ++){ if(g_game_layers[iLoop].visible == 1){ switch(iLoop){ case lyrEnemy: SDL_BlitSurface( g_enemy_image.image, &g_enemy_image.src_rect, video_surface, &g_enemy_image.dst_rect ); break; case lyrCommandtxt_1: draw_command_text(g_game_layers, lyrCommandwin_1, lyrCommandtxt_1, video_surface); break; case lyrStatustxt_1: draw_status_text(g_game_layers, lyrStatuswin_1, lyrStatustxt_1, video_surface); break; default: /* サーフェスの複写 */ SDL_BlitSurface( g_game_layers[iLoop].surface, &g_game_layers[iLoop].src_rect, video_surface, &g_game_layers[iLoop].dst_rect ); break; } } } /* サーフェスフリップ */ SDL_Flip(video_surface); /* アップデートフラグの初期化 */ reset_update_display_flag(); return interval; } /***************************************************************** * レイヤー作成 * *****************************************************************/ /* ウインドウ作成 */ int create_msg_window(type_layer *layer, int index, int x, int y, int w, int h){ init_layer_rect(layer, index); SDL_Surface *tmp_surface; SDL_Rect tmp_rect; /* サーフェス作成 */ tmp_surface = SDL_CreateRGBSurface(SDL_HWSURFACE, w, h, 32, 0, 0, 0, 0); if(tmp_surface == NULL){ return -1; } /* ウインドウ作成(ボーダー色) */ tmp_rect.x = 0; tmp_rect.y = 0; tmp_rect.w = w; tmp_rect.h = h; SDL_FillRect(tmp_surface, &tmp_rect, MSG_WIN_BORDER_COLOR(tmp_surface->format)); /* ウインドウ作成(ウインドウ色) */ tmp_rect.x = MSG_WIN_BORDER; tmp_rect.y = MSG_WIN_BORDER; tmp_rect.w = w - MSG_WIN_BORDER * 2; tmp_rect.h = h - MSG_WIN_BORDER * 2; SDL_FillRect(tmp_surface, &tmp_rect, MSG_WIN_COLOR(tmp_surface->format)); /* 透過設定 */ SDL_SetAlpha(tmp_surface, SDL_SRCALPHA, MSG_WIN_OPACITY); (layer + index)->surface = SDL_DisplayFormat(tmp_surface); SDL_FreeSurface(tmp_surface); update_layer_rect_size(layer, index); update_layer_rect_pos(layer, index, x, y); 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[iLoop].visible = 0; } /* バックグラウンド */ g_game_layers[lyrBackground].surface = IMG_Load("bg.jpg"); update_layer_rect_size(g_game_layers, lyrBackground); g_game_layers[lyrBackground].use = 1; set_layer_visible(g_game_layers, lyrBackground, 1); /* 敵 */ g_enemy_image.image = IMG_Load("penguin_nuke_anim.png"); /* (手書き) */ g_enemy_image.src_rect.w = 212; g_enemy_image.src_rect.h = 240; g_enemy_image.src_rect.x = 0; g_enemy_image.src_rect.y = 0; g_enemy_image.dst_rect.x = 200; g_enemy_image.dst_rect.y = 40; g_game_layers[lyrEnemy].use = 1; set_layer_visible(g_game_layers, lyrEnemy, 1); /* ウインドウ */ create_msg_window(g_game_layers, lyrCommandwin_1, BATTLE_COMMAND_WIN_X, BATTLE_COMMAND_WIN_Y, BATTLE_COMMAND_WIN_W, BATTLE_COMMAND_WIN_H); g_game_layers[lyrCommandwin_1].use = 1; set_layer_visible(g_game_layers, lyrCommandwin_1, 1); create_msg_window(g_game_layers, lyrStatuswin_1, BATTLE_STATUS_WIN_X, BATTLE_STATUS_WIN_Y, BATTLE_STATUS_WIN_W, BATTLE_STATUS_WIN_H); g_game_layers[lyrStatuswin_1].use = 1; set_layer_visible(g_game_layers, lyrStatuswin_1, 1); create_msg_window(g_game_layers, lyrTopwin, BATTLE_TOP_WIN_X, BATTLE_TOP_WIN_Y, BATTLE_TOP_WIN_W, BATTLE_TOP_WIN_H); g_game_layers[lyrTopwin].use = 1; set_layer_visible(g_game_layers, lyrTopwin, 1); set_layer_visible(g_game_layers, lyrCommandtxt_1, 1); set_layer_visible(g_game_layers, lyrStatustxt_1, 1); return 0; } /* テキスト読み込み */ int load_text(){ SDL_RWops* ope; int iLoop = 0; ope = file_open_for_read_text(TEXT_COMMAND); for(iLoop = 0; iLoop < cmdEnd; iLoop++){ file_read_line(ope, g_achCommands[iLoop], sizeof(g_achCommands[iLoop])); } file_close(ope); ope = file_open_for_read_text(TEXT_COMMON); for(iLoop = 0; iLoop < btlcmnEnd; iLoop++){ file_read_line(ope, g_achCommonText[iLoop], sizeof(g_achCommonText[iLoop])); } file_close(ope); ope = file_open_for_read_text(TEXT_NAME); file_read_line(ope, g_achCharacterNames[0], sizeof(g_achCharacterNames[0])); file_close(ope); return 0; } /* テスト用関数 */ int test_init(){ g_my_status.hp = 1000; g_my_status.mhp = 1000; g_my_status.mp = 1000; g_my_status.mmp = 1000; return 0; } /* メイン関数 */ int main(int argc, char* argv[]){ SDL_Event event; SDL_TimerID timer_id, display_timer_id; int exit_prg = 0; test_init(); SDL_Init(SDL_INIT_EVERYTHING); TTF_Init(); SDL_SetVideoMode(WINDOW_WIDTH, WINDOW_HEIGHT, BPP, SDL_HWSURFACE); /* テキストファイルの読み込み */ load_text(); /* フォント読み込み */ font = TTF_OpenFont(FONT_NAME, FONT_SIZE); /* 画像読み込み */ init_layers(); /* 描画 */ set_update_display_flag(); update_display(0, NULL); /* 初期描画 */ timer_id = SDL_AddTimer(1000, enemy_animation, &g_enemy_image); display_timer_id = SDL_AddTimer(200, update_display, NULL); /* イベントループ */ while(exit_prg == 0){ if(SDL_PollEvent(&event)){ switch(event.type){ case SDL_KEYDOWN: switch(event.key.keysym.sym){ case SDLK_UP: break; case SDLK_DOWN: break; case SDLK_RIGHT: break; case SDLK_LEFT: break; case SDLK_ESCAPE: exit_prg = 1; break; case SDLK_SPACE: break; default: break; } break; default: break; } } SDL_Delay(1); } SDL_RemoveTimer(display_timer_id); SDL_RemoveTimer(timer_id); drop_surface(g_enemy_image.image); drop_layers(); TTF_CloseFont(font); TTF_Quit(); SDL_Quit(); return 0; }
青が描画、緑がファイル読み込み関係で主に今回追加したものです。
注意:すべてUTF-8、改行コードはLFとしてください。ファイルの終端は必ず改行で終わるようにしてください。
コマンド名を列挙したものです。プログラムソース内のenm_command列挙体の順序と対応しています。
戦う 魔法 防御 アイテム 逃げる
戦闘中に表示される共通メッセージなどを書いたものです。今回はステータス表示のフォーマットのみ格納されています。
%s HP %4d/%4d MP %4d/%4d
主人公名が書かれています。何でもいいんですけどね。
ライアソ
テキストファイルの中身は適当に変えていいですが、指定された文字列サイズ(#define BYTE_****で定義した値)以下になるようにしてください(現在エラー処理等が入っていません)。UTF-8の場合、1文字が2バイト、3バイトになる可能性があります。
HP、MPは今回はソースコードにべた書きしています(test_init()内で設定)。
ファイルの読み込みはfile_open_for_read_text()、file_read_line()、file_close()の3点セットで行っています。オープンとクローズはそのままSDL_RWops操作関数を呼んだもの、file_read_line()はfile_read_byte()で一文字ずつ読み取って改行コードをチェックしています。
テキストレイヤー描画の時に、draw_***_text()でテキストとウインドウのレイヤー番号を指定して、ウインドウの位置からテキストの配置を計算するようになっています。
type_status構造体が主人公や敵のステータスとなります。今は表示用のHPとMPしかありませんが、戦闘をするときには攻撃力や守備力などのステータスが必要になります。
← Windows
← Linux