SDL2でRPGゲームを作る 〜第18回 扉を開く〜
前回は宝箱を開ける方法を考えた。中身を保持することはまだできないので追々実装していく。
今回は開けるつながりで扉を開く方法を考えていく。
扉を開くには
まず、扉用の構造体を用意した。
typedef struct { int map_x; int map_y; int status; } DOOR;
DOOR
構造体が持っているのは扉の位置と状態(開いているか閉じているか)である。
状態は
- 0: 閉じている
- 1: 開いている
とした。
宝箱と違う点は、一回開いたものを開きっぱなしにする必要はないと思うので、フィールドを読み込むたびに閉まっている状態に戻る。
イベントファイルには以下のように記載し宝箱などと同様にフィールド切り替えのタイミングで読み込まれるようにした。
# DOOR, マスX座標, マスY座標 DOOR,9,10 DOOR,3,4 DOOR,3,12 DOOR,16,4 DOOR,16,12
イベントファイルの読み込み
扉の設置の方法が決まったところで、まず、イベントファイルを読み込みだ。
イベントファイルを読み込む方法は前回の宝箱と同じ方式なので、それをベースに扉用に作っていく。
状態として一旦初期値は9
にして、イベントファイルで読み込んだ部分のみ閉じている状態つまり0
にする。
これは、描画するときに配列の0
と1
のみを対象にするためである。
int load_door(SDL_Renderer *renderer) { char event_path[256] = {0}; sprintf(event_path, "data/%s.evt", MAP_EVENT_NAME); FILE *fp; char event[256] = {0}; int map_x; int map_y; char buf[256] = {0}; int i; fp = fopen(event_path, "r"); if (fp == NULL) { printf("file open error. %d\n", __LINE__); return 1; } for (i = 0;i < sizeof(door)/sizeof(door[0]);i++) { door[i].status = 9; } for (i = 0;fgets(buf, sizeof(buf), fp) != NULL;i++) { if (strncmp(buf, "#", 1) != 0){ if (strncmp(buf, "DOOR", 4) == 0) { sscanf(buf, "%[^,],%d,%d", event, &map_x, &map_y); door[i].map_x = map_x; door[i].map_y = map_y; door[i].status = 0; } } } fclose(fp); return 0; }
扉の状態変更
次は、扉の状態を変える関数。目の前に扉があれば状態を「開く(status = 1)」に変化させる。
int open_door() { int i; char *se_file = "door.ogg"; for(i = 0;i<sizeof(door)/sizeof(door[0]);i++){ if (player.direction == UP) { if (door[i].map_x == player.map_x && door[i].map_y == player.map_y - 1) { door[i].status = 1; sound_se(se_file); return 0; } } else if (player.direction == DOWN) { if (door[i].map_x == player.map_x && door[i].map_y == player.map_y + 1) { door[i].status = 1; sound_se(se_file); return 0; } } else if (player.direction == RIGHT) { if (door[i].map_x == player.map_x + 1 && door[i].map_y == player.map_y) { door[i].status = 1; sound_se(se_file); return 0; } } else if (player.direction == LEFT) { if (door[i].map_x == player.map_x - 1 && door[i].map_y == player.map_y) { door[i].status = 1; sound_se(se_file); return 0; } } } message_window_status(); message = DOOR_MESSAGE; return 0; }
扉の描画
最後に状態を見て扉を描画する。
int draw_door(SDL_Renderer *renderer, SDL_Event e){ int i; for(i = 0;i<sizeof(door)/sizeof(door[0]);i++){ if (door[i].status != 9) { SDL_Rect imageRect=(SDL_Rect){0, 0, IMAGE_WIDTH, IMAGE_HEIGHT}; SDL_Rect drawRect=(SDL_Rect){(door[i].map_x * GRID_SIZE) - player.offset_x, (door[i].map_y * GRID_SIZE) - player.offset_y, IMAGE_WIDTH*MAGNIFICATION, IMAGE_HEIGHT*MAGNIFICATION}; if (door[i].status == 0){ SDL_RenderCopy(renderer, mapchip[17].map_image, &imageRect, &drawRect); } } } return 0; }
全コード表示
では、いつもの通り全コードを表示する。
扉が開くようになった
実行結果
扉を開けられるようになった。
終わりに
大体宝箱と同じ方法で扉を実装することができた。文章をまとめながらもうちょっとうまく作れたかなぁと思ったけどとりあえず実現できたから良しとする。気が向いたらリファクタリングする。
次回は、コマンドウィンドウあたりを作っていきたい。
SDL2でRPGゲームを作る 〜第17回 宝箱を開ける〜
前回はBGM・SEを鳴らす方法を考えた。音が出るようになると、またRPG感がぐっと出てくる。
この調子で少しずつでも作っていきたい。といったところで、今回は宝箱を開ける方法を考えていく。
宝箱を開けるには
まず、宝箱を開けるために用意した構造体が以下の2つ。
typedef struct { int map_x; int map_y; char item[128]; int status; } TREASURE; typedef struct { char map[256]; TREASURE treasure[256]; } TREASURE_FRAME;
TREASURE
構造体は宝箱の位置と中身と状態(開いているか閉じているか)を持っている。
TREASURE_FRAME
構造体はマップ名とTREASURE
構造体を持つ形になっている。
2つの構造体に分けた理由はマップごとに宝箱の情報を持つ形にしたかったからである。
というのも、以下のように宝箱を設置できるようにしたかったからだ。
# TREASURE, アイテムID, マスX座標, マスY座標, 中身 TREASURE,0,8,8,10G TREASURE,1,9,8,薬草 TREASURE,2,10,8,フライパン
上記は、->第7回 マップ間移動で導入したイベントファイルになる。
ここに、設置したい座標と中身を書くことでマップ上に表示されるようにする。アイテムIDを持たせたのは開閉の状態を
宝箱に持たすためでマップ+アイテムID
で一意になるようにしている。これがあることで、一度開けた宝箱がからっぽになる
という状態を表現できる。
イベントファイルの読み込みと宝箱の描画
では、宝箱の構造と設置の方法が決まったところで今度は読み込みと描画だが、基本的なところは今まで作った イベントファイルを読み込む方法とそれを描画する方法と同じなので、それをベースに宝箱用に作っていく。
まずは、イベントファイルから宝箱の情報を読み込む関数。
int load_treasure(SDL_Renderer *renderer) { char event_path[256] = {0}; sprintf(event_path, "data/%s.evt", MAP_EVENT_NAME); FILE *fp; char event[256] = {0}; int item_id; int map_x; int map_y; char item[128] = {0}; int item_length; char buf[256] = {0}; int i, j; int new_flg = 0; fp = fopen(event_path, "r"); if (fp == NULL) { printf("file open error. %d\n", __LINE__); return 1; } for (i = 0;i < sizeof(treasure)/sizeof(treasure[0]);i++) { if (strcmp(treasure[i].map, MAP_EVENT_NAME) == 0) { new_flg = 1; } } if (new_flg == 0) { i = 0; while (1) { if (strcmp(treasure[i].map, "empty") == 0) { sprintf(treasure[i].map, "%s", MAP_EVENT_NAME); break; } i++; } } for (i = 0;fgets(buf, sizeof(buf), fp) != NULL;i++) { if (strncmp(buf, "#", 1) != 0){ if (strncmp(buf, "TREASURE", 8) == 0) { sscanf(buf, "%[^,],%d,%d,%d,%[^,]", event, &item_id, &map_x, &map_y, item); for (j = 0;j < sizeof(treasure)/sizeof(treasure[0]);j++) { if (strcmp(treasure[j].map, MAP_EVENT_NAME) == 0) { if (treasure[j].treasure[item_id].status != 1) { treasure[j].treasure[item_id].map_x = map_x; treasure[j].treasure[item_id].map_y = map_y; treasure[j].treasure[item_id].status = 0; item_length = strlen(item); item[item_length - 1] = '\0'; sprintf(treasure[j].treasure[item_id].item, "%s", item); } } } } } } fclose(fp); return 0; }
まず、treasure構造体配列のmap
はすべてempty
という文字列で初期化している。
これは、新規のマップに行ったときに新しくマップ情報及び宝箱情報を保持するためにそうしている。
このあたりの処理は、new_flg
を使用したループ文で書いている。
次に、treasure構造体配列はマップ名の配列があり、その下に宝箱の配列がぶら下がっているような作りなので
treasure[j].treasure[item_id].status
のようにして、マップ+アイテムID
の組み合わせで情報を保持している。
次は、読み込んだ情報をもとに宝箱を描画する関数。
int draw_treasure(SDL_Renderer *renderer){ int i, j; for(i = 0;i<sizeof(treasure)/sizeof(treasure[0]);i++){ if (strcmp(treasure[i].map, MAP_EVENT_NAME) == 0) { for(j = 0;j<sizeof(treasure->treasure)/sizeof(treasure->treasure[0]);j++){ if (strcmp(treasure[i].treasure[j].item, "empty") != 0) { SDL_Rect imageRect=(SDL_Rect){0, 0, IMAGE_WIDTH, IMAGE_HEIGHT}; SDL_Rect drawRect=(SDL_Rect){(treasure[i].treasure[j].map_x * GRID_SIZE) - player.offset_x, (treasure[i].treasure[j].map_y * GRID_SIZE) - player.offset_y, IMAGE_WIDTH*MAGNIFICATION, IMAGE_HEIGHT*MAGNIFICATION}; if (treasure[i].treasure[j].status == 0){ SDL_RenderCopy(renderer, mapchip[15].map_image, &imageRect, &drawRect); } else { SDL_RenderCopy(renderer, mapchip[16].map_image, &imageRect, &drawRect); } } } } } return 0; }
こちらは、宝箱の状態を判断してstatus=0
であれば閉じている宝箱、status=1
であれば開いている宝箱を表示する。
宝箱を開いたときの処理
宝箱の前でSPACEを押したときに中身を取得できるようにすることにした。
処理のタイミングとしてはメッセージ表示のタイミングで呼び出すこととした。
いつものごとく、全コードを参照できるようにするのでそちらで確認してみてほしい。
int get_treasure_message(char **message) { int i, j; char *se_file = "treasure.ogg"; for (i = 0;i < sizeof(treasure)/sizeof(treasure[0]);i++) { for (j = 0;j < sizeof(treasure->treasure)/sizeof(treasure->treasure[0]);j++) { if ((treasure[i].treasure[j].map_x == player.map_x && treasure[i].treasure[j].map_y == player.map_y - 1 && strcmp(treasure[i].map, MAP_EVENT_NAME) == 0) || (treasure[i].treasure[j].map_x == player.map_x && treasure[i].treasure[j].map_y == player.map_y + 1 && strcmp(treasure[i].map, MAP_EVENT_NAME) == 0) || (treasure[i].treasure[j].map_x == player.map_x + 1 && treasure[i].treasure[j].map_y == player.map_y && strcmp(treasure[i].map, MAP_EVENT_NAME) == 0) || (treasure[i].treasure[j].map_x == player.map_x - 1 && treasure[i].treasure[j].map_y == player.map_y && strcmp(treasure[i].map, MAP_EVENT_NAME) == 0) ) { if (treasure[i].treasure[j].status == 0) { *message = strncat(treasure[i].treasure[j].item, "を手に入れた!", 21); treasure[i].treasure[j].status = 1; } else { *message = "からっぽ!"; } sound_se(se_file); } } } return 0; }
プレイヤーの正面に宝箱がある場合にメッセージを表示して状態を変化させる。
全コード表示
そろそろ、行数も増えてきて全コード記載するのがしんどくなってきたのでGitHubにソースをあげてみた。
こっちのほうが、変更がしっかり管理できていい感じである。
宝箱はこのあたり
宝箱はこのあたり
ただし、BLOGにまとめるのは時間がかかるといったことから結構サボってるので、GitHubのほうのソースが結構先行して
しまっている。
実行結果
宝箱を開けられるようになった。
終わりに
宝箱の状態を保持する方法がなかなか思いつかなくて結構苦戦したが、とりあえず実装できてよかった。
今回は宝箱の開閉部分だけをつくったが、最終的には取得したアイテムを保持して使うところまで実装していきたい。
次回は、扉を開けられるようにしたいと思う。
SDL2でRPGゲームを作る 〜第16回 BGM・SEを入れる〜
前回まで4回に渡ってメッセージボックスを作成した。今回は、BGM・SEを入れるにはどうするかを考えていく。
音を鳴らすための準備
過去にSDL2で音を鳴らすという記事を書いたが、これに従って実装していく。
まず、ヘッダファイルとして以下を追加する。
#include <SDL2/SDL_mixer.h>
これで、音を扱うライブラリを使うことができる。
次に、BGMを扱うためにSDL_mixerの初期化を行う。
Mix_Music *music = NULL; // Initialize Mixer if (SDL_Init(SDL_INIT_AUDIO) < 0) { printf( "Mixer could not initialize! Mixer_Error: %s\n", Mix_GetError()); return 1; } if( Mix_OpenAudio( 22050, MIX_DEFAULT_FORMAT, 2, 4096 ) == -1 ) { printf( "Mix_OpenAudio could not initialize! Mixer_Error: %s\n", Mix_GetError()); return 1; } music = Mix_LoadMUS("music/bgm/village02.ogg"); if (music == NULL) { return 1; } if (Mix_PlayMusic(music, -1) == -1) { return 1; } Mix_VolumeMusic(10); Mix_PlayingMusic();
処理の最後には、SDL_mixerの終了処理を書いておく。
// quit SDL_mixer
Mix_FreeMusic(music);
Mix_CloseAudio();
これで、準備OK。
BGMを鳴らす
マップごとにBGMを定義したいのでBGMを鳴らす関数を用意する。 定義するのは第7回 マップ間移動 で作成したイベントファイルに以下のように記載する。
# BGM, 音楽ファイル(mp3, ogg, wavなど使用可) # 読込みのために最後のカンマを忘れないこと BGM,filed.ogg,
このイベントファイルからファイル名を読み取ってBGMを鳴らす関数がこちら。
int load_bgm(void) { char event_path[256] = {0}; sprintf(event_path, "data/%s.evt", MAP_EVENT_NAME); FILE *fp; char buf[256] = {0}; char event[256] = {0}; char bgm_name[256] = {0}; char bgm_path[256] = {0}; int i = 0; fp = fopen(event_path, "r"); if (fp == NULL) { printf("file open error. %d\n", __LINE__); return 1; } for(i = 0;fgets(buf, sizeof(buf), fp) != NULL;i++) { if (strncmp(buf, "#", 1) != 0){ if (strncmp(buf, "BGM", 3) == 0) { sscanf(buf, "%[^,],%[^,]", event, &bgm_name); sprintf(bgm_path, "music/bgm/%s", bgm_name); music = Mix_LoadMUS(bgm_path); if (music == NULL) { return 1; } if (Mix_PlayMusic(music, -1) == -1) { return 1; } break; } } } fclose(fp); return 0; }
これで、マップ移動のタイミングで任意のBGMが鳴らせるようになった。
SEを鳴らす
続いて、SEを鳴らす関数を作っていく。SEを鳴らす関数はイベントファイルから読み込むものとSEファイルを直接鳴らすものと2つ作成した。
# SE, マスX座標, マスY座標, SEファイル(ogg, wavなど使用可) # 読込みのために最後のカンマを忘れないこと SE,2,2,upstairs.ogg, SE,5,5,upstairs.ogg, SE,15,12,upstairs.ogg,
イベントファイルから読み込む関数が以下。
int load_se(void) { char event_path[256] = {0}; sprintf(event_path, "data/%s.evt", MAP_EVENT_NAME); FILE *fp; char buf[256] = {0}; char event[256] = {0}; int event_point_x; int event_point_y; char *se_name; char se_path[256] = {0}; int i = 0; Mix_Chunk *wave = NULL; fp = fopen(event_path, "r"); if (fp == NULL) { printf("file open error. %d\n", __LINE__); return 1; } if ((se_name = malloc(sizeof(char) * 256)) == NULL) { printf("file open error. %d\n", __LINE__); return 1; } for(i = 0;fgets(buf, sizeof(buf), fp) != NULL;i++) { if (strncmp(buf, "#", 1) != 0){ if (strncmp(buf, "SE", 2) == 0) { sscanf(buf, "%[^,],%d,%d,%[^,]", event, &event_point_x, &event_point_y, se_name); if (player.map_x == event_point_x && player.map_y == event_point_y) { sprintf(se_path, "music/se/%s", se_name); wave = Mix_LoadWAV(se_path); if (wave == NULL) { return 1; } Mix_VolumeChunk(wave, 10); if ( Mix_PlayChannel(-1, wave, 0) == -1 ) { return 1; } break; } } } } fclose(fp); free(se_name); return 0; }
SEファイルを直接鳴らすのが以下になる。
int sound_se(char *se_name) { Mix_Chunk *wave = NULL; char se_path[256] = {0}; sprintf(se_path, "music/se/%s", se_name); wave = Mix_LoadWAV(se_path); if (wave == NULL) { return 1; } Mix_VolumeChunk(wave, 10); if ( Mix_PlayChannel(-1, wave, 0) == -1 ) { return 1; } return 0; }
使い分けとしては、基本的にはイベントファイルで表せるものはload_se
、メッセージのピロピロ音などのイベントファイルに記載できないSEはsound_se
を使うようにする。
全コード表示
では、いつものごとく全コードを表示する。
Makefile
all: RPGInC RPGInC: gcc -g -o RPGInC RPGInC.c RPGInC.h `sdl2-config --cflags --libs` -lSDL2_image -lSDL2_ttf -lSDL2_mixer run: ./RPGInC map: gcc -g -o MAPCreater MAPCreater.c `sdl2-config --cflags --libs` -lSDL2_image -lSDL2_ttf runmap: ./MAPCreater clean: rm -f RPGInC
RPGInC.h
#ifdef _RPGINC #else #include <stdio.h> #include <SDL2/SDL.h> #include <SDL2/SDL_image.h> #include <SDL2/SDL_ttf.h> typedef enum {DOWN, LEFT, RIGHT, UP} DIRECTION; typedef enum {FALSE, TRUE} MOVING; typedef enum {OUT_VISIBLE, IN_VISIBLE} VISIBLE; typedef enum {OFF, ON} STATE; typedef struct { int map_x; int map_y; int pixel_x; int pixel_y; int offset_x; int offset_y; int velocity_x; int velocity_y; DIRECTION direction; MOVING moving; } CARACTER; typedef struct { CARACTER npc; char message[1024]; SDL_Texture *npc_image; MOVING npc_move; } NPC; typedef struct { int mapchip_id; char mapchip_name[256]; int movable; int change_locate; SDL_Texture *map_image; } MAPCHIP; typedef struct { int rectangle_x; int rectangle_y; int rectangle_w; int rectangle_h; int blend; VISIBLE visible; } WINDOW; int clac_offset(int, int, int *, int *); int load_image(SDL_Renderer *, SDL_Texture **, char *); int player_animation(SDL_Renderer *); int player_update(SDL_Renderer *, SDL_Event); int player_move(SDL_Event); int load_npc(SDL_Renderer *); int npc_animation(SDL_Renderer *); int npc_update(SDL_Renderer *renderer, int); int npc_move(DIRECTION, int); int load_map_image(SDL_Renderer *, SDL_Texture **); int load_mapchip(SDL_Renderer *); int load_move(SDL_Renderer *); int load_se(void); int load_bgm(void); int sound_se(char *); int load_map(char *); int draw_map(SDL_Renderer *); int is_movable(int, int); int fade_out(SDL_Renderer *); int make_window(SDL_Renderer *, WINDOW); int make_box(SDL_Renderer *, int, int, int, int, int, int, int, int); int make_triangle(SDL_Renderer *, int, int, int, int, int, int, int, int, int, int); int window_update(SDL_Renderer *, TTF_Font *, SDL_Event); int window_engine(SDL_Renderer *, WINDOW); int message_engine(SDL_Renderer *, TTF_Font *, SDL_Event); int display_character_string(SDL_Renderer *, TTF_Font *, char *, double, double); int get_character_message(SDL_Event, char **); int u8mb(const char); int flash_triangle(SDL_Renderer *); #endif
RPGInC.c
#include <stdio.h> #include <stdlib.h> #include <time.h> #include <SDL2/SDL.h> #include <SDL2/SDL_image.h> #include <SDL2/SDL_ttf.h> #include <SDL2/SDL_mixer.h> #include "RPGInC.h" #define FONT_PATH "font/PixelMplus12-Regular.ttf" const int SCREEN_WIDTH = 640; const int SCREEN_HEIGHT = 480; const int IMAGE_WIDTH = 16; const int IMAGE_HEIGHT = 16; const int MAGNIFICATION = 2; const int GRID_SIZE = 32; const int FONT_SIZE = 16; int ROW = 15; int COL = 20; int OUT_OF_MAP = 0; char MAP_EVENT_NAME[256] = "field"; int animecycle = 24; int speed = 2; int frame = 0; int number_of_map_image = 0; int number_of_npc_image = 0; CARACTER player = {1, 1, 32, 32, 0, 0, 0, 0, DOWN, FALSE}; NPC npc[256] = {0}; MAPCHIP mapchip[256] = {0}; int *map_array; WINDOW message_window = {140, 334, 360, 140, 255, OUT_VISIBLE}; char *message; STATE state = OFF; Mix_Music *music = NULL; int main (int argc, char *argv[]) { SDL_Window *window = NULL; SDL_Renderer *renderer = NULL; TTF_Font *font = NULL; // Initialize SDL if( SDL_Init( SDL_INIT_VIDEO ) < 0 ) { printf( "SDL could not initialize! SDL_Error: %s\n", SDL_GetError() ); return 1; } window = SDL_CreateWindow( "DRAW IMAGE TEST", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN ); if( window == NULL ) { printf( "Window could not be created! SDL_Error: %s\n", SDL_GetError() ); return 1; } else { renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); } load_mapchip(renderer); load_map("data/field.map"); load_npc(renderer); // // Initialize TTF if ( TTF_Init() < 0 ) { printf("TTF could not initialize! TTF_Error: %s\n", TTF_GetError()); } font = TTF_OpenFont(FONT_PATH, FONT_SIZE); if ( font == NULL ) { printf("TTF_OpenFont: %s\n", TTF_GetError()); } // // Initialize Mixer if (SDL_Init(SDL_INIT_AUDIO) < 0) { printf( "Mixer could not initialize! Mixer_Error: %s\n", Mix_GetError()); return 1; } if( Mix_OpenAudio( 22050, MIX_DEFAULT_FORMAT, 2, 4096 ) == -1 ) { printf( "Mix_OpenAudio could not initialize! Mixer_Error: %s\n", Mix_GetError()); return 1; } music = Mix_LoadMUS("music/bgm/village02.ogg"); if (music == NULL) { return 1; } if (Mix_PlayMusic(music, -1) == -1) { return 1; } Mix_VolumeMusic(10); Mix_PlayingMusic(); // // main loop while (1) { SDL_Delay(5); SDL_Event e; clac_offset(player.pixel_x, player.pixel_y, &player.offset_x, &player.offset_y); SDL_RenderClear(renderer); draw_map(renderer); npc_animation(renderer); player_animation(renderer); player_update(renderer, e); window_update(renderer, font, e); SDL_RenderPresent(renderer); // event handling if ( SDL_PollEvent(&e) ) { if (e.type == SDL_QUIT){ break; } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE){ break; } } } IMG_Quit(); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); int i; for (i = 0;i < number_of_map_image;i++) { SDL_DestroyTexture(mapchip[i].map_image); } for (i = 0;i < number_of_npc_image;i++) { SDL_DestroyTexture(npc[i].npc_image); } SDL_Quit(); // quit SDL_mixer Mix_FreeMusic(music); Mix_CloseAudio(); return 0; } int load_image(SDL_Renderer *renderer, SDL_Texture **image_texture, char *filename) { SDL_Surface *image = NULL; // 画像の読み込み image = IMG_Load(filename); if(!image) { printf("IMG_Load: %s\n", IMG_GetError()); return 1; } // 透過色の設定 SDL_SetColorKey(image, SDL_TRUE, SDL_MapRGB(image->format, 255, 0, 255)); *image_texture = SDL_CreateTextureFromSurface(renderer, image); SDL_FreeSurface(image); return 0; } int player_animation(SDL_Renderer *renderer) { SDL_Texture *cat_image = NULL; load_image(renderer, &cat_image, "image/charachip/chiharu.bmp"); // load_image(renderer, &cat_image, "image/charachip/white_cat.bmp"); int x = ((frame / animecycle) % 4) * 16; int y = player.direction * IMAGE_HEIGHT; SDL_Rect imageRect=(SDL_Rect){x, y, IMAGE_WIDTH, IMAGE_HEIGHT}; SDL_Rect drawRect=(SDL_Rect){player.pixel_x - player.offset_x, player.pixel_y - player.offset_y, IMAGE_WIDTH*MAGNIFICATION, IMAGE_HEIGHT*MAGNIFICATION}; SDL_RenderCopy(renderer, cat_image, &imageRect, &drawRect); if (frame <= animecycle * 4) { frame += 1; } else{ frame = 0; } SDL_DestroyTexture(cat_image); return 0; } int player_update(SDL_Renderer *renderer, SDL_Event e) { if (player.moving == TRUE) { player.pixel_x = player.pixel_x + player.velocity_x; player.pixel_y = player.pixel_y + player.velocity_y; if (player.pixel_x % GRID_SIZE == 0 && player.pixel_y % GRID_SIZE == 0){ player.moving = FALSE; player.map_x = player.pixel_x / GRID_SIZE; player.map_y = player.pixel_y / GRID_SIZE; load_move(renderer); player_move(e); } } else { player_move(e); } } int player_move(SDL_Event e) { if (message_window.visible == OUT_VISIBLE) { if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_UP){ player.direction = UP; if (is_movable(player.map_x, player.map_y - 1) == 0) { player.velocity_x = 0; player.velocity_y = -speed; player.moving = TRUE; } } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_DOWN){ player.direction = DOWN; if (is_movable(player.map_x, player.map_y + 1) == 0) { player.velocity_x = 0; player.velocity_y = speed; player.moving = TRUE; } } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_RIGHT){ player.direction = RIGHT; if (is_movable(player.map_x + 1, player.map_y) == 0) { player.velocity_x = speed; player.velocity_y = 0; player.moving = TRUE; } } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_LEFT){ player.direction = LEFT; if (is_movable(player.map_x - 1, player.map_y) == 0) { player.velocity_x = -speed; player.velocity_y = 0; player.moving = TRUE; } } } return 0; } int load_npc(SDL_Renderer *renderer) { char event_path[256] = {0}; sprintf(event_path, "data/%s.evt", MAP_EVENT_NAME); FILE *fp; char event[256] = {0}; char npc_name[256] = {0}; int map_x; int map_y; DIRECTION direction; MOVING moving; int max_step; int message_length; char message[1024] = {0}; char buf[1024] = {0}; char npc_path[256] = {0}; int i = 0; int element_number = 0; fp = fopen(event_path, "r"); if (fp == NULL) { printf("file open error. %d\n", __LINE__); return 1; } for (i = 0;i < number_of_npc_image;i++) { npc[i].npc.map_x = 0; npc[i].npc.map_y = 0; npc[i].npc.pixel_x = 0; npc[i].npc.pixel_y = 0; npc[i].npc.direction = 0; npc[i].npc.moving = 0; SDL_DestroyTexture(npc[i].npc_image); sprintf(npc[i].message, "%s", '\0'); } for(i = 0;fgets(buf, sizeof(buf), fp) != NULL;i++) { if (strncmp(buf, "#", 1) != 0){ if (strncmp(buf, "CHARA", 5) == 0) { sscanf(buf, "%[^,],%[^,],%d,%d,%d,%d,%[^,]", event, npc_name, &map_x, &map_y, &direction, &moving, message); sprintf(npc_path, "image/charachip/%s.bmp", npc_name); load_image(renderer, &npc[element_number].npc_image, npc_path); npc[element_number].npc.map_x = map_x; npc[element_number].npc.map_y = map_y; npc[element_number].npc.pixel_x = map_x * GRID_SIZE; npc[element_number].npc.pixel_y = map_y * GRID_SIZE; npc[element_number].npc.direction = direction; npc[element_number].npc_move = moving; message_length = strlen(message); message[message_length - 1] = '\0'; sprintf(npc[element_number].message, "%s", message); element_number += 1; } } } number_of_npc_image = element_number; fclose(fp); return 0; } int npc_animation(SDL_Renderer *renderer) { int i; for(i = 0; number_of_npc_image >= i;i++) { int x = ((frame / animecycle) % 4) * 16; int y = npc[i].npc.direction * IMAGE_HEIGHT; SDL_Rect imageRect=(SDL_Rect){x/4, y/4, IMAGE_WIDTH/4, IMAGE_HEIGHT/4}; SDL_Rect drawRect=(SDL_Rect){npc[i].npc.pixel_x - player.offset_x, npc[i].npc.pixel_y - player.offset_y, IMAGE_WIDTH*MAGNIFICATION, IMAGE_HEIGHT*MAGNIFICATION}; SDL_RenderCopy(renderer, npc[i].npc_image, &imageRect, &drawRect); if (npc[i].npc_move == TRUE && number_of_npc_image != 0) { npc_update(renderer, i); } } return 0; } int npc_update(SDL_Renderer *renderer, int element) { srand((unsigned)time(NULL)); int target = rand()%number_of_npc_image; int action = rand()%10; if (npc[element].npc_move == TRUE) { if (npc[element].npc.moving == TRUE) { npc[element].npc.pixel_x = npc[element].npc.pixel_x + npc[element].npc.velocity_x; npc[element].npc.pixel_y = npc[element].npc.pixel_y + npc[element].npc.velocity_y; if (npc[element].npc.pixel_x % GRID_SIZE == 0 && npc[element].npc.pixel_y % GRID_SIZE == 0) { npc[element].npc.moving = FALSE; npc[element].npc.map_x = npc[element].npc.pixel_x / GRID_SIZE; npc[element].npc.map_y = npc[element].npc.pixel_y / GRID_SIZE; } } else { if (target == element && action < 3) { npc_move(rand()%4, element); } } } } int npc_move(DIRECTION direction, int element) { if (message_window.visible == OUT_VISIBLE) { if (frame == 0) { if (direction == UP){ npc[element].npc.direction = UP; if (is_movable(npc[element].npc.map_x, npc[element].npc.map_y - 1) == 0) { npc[element].npc.velocity_x = 0; npc[element].npc.velocity_y = -speed; npc[element].npc.moving = TRUE; } } else if (direction == DOWN){ npc[element].npc.direction = DOWN; if (is_movable(npc[element].npc.map_x, npc[element].npc.map_y + 1) == 0) { npc[element].npc.velocity_x = 0; npc[element].npc.velocity_y = speed; npc[element].npc.moving = TRUE; } } else if (direction == RIGHT){ npc[element].npc.direction = RIGHT; if (is_movable(npc[element].npc.map_x + 1, npc[element].npc.map_y) == 0) { npc[element].npc.velocity_x = speed; npc[element].npc.velocity_y = 0; npc[element].npc.moving = TRUE; } } else if (direction == LEFT){ npc[element].npc.direction = LEFT; if (is_movable(npc[element].npc.map_x - 1, npc[element].npc.map_y) == 0) { npc[element].npc.velocity_x = -speed; npc[element].npc.velocity_y = 0; npc[element].npc.moving = TRUE; } } } } return 0; } int draw_map(SDL_Renderer *renderer){ int x, y; int start_x = player.offset_x / GRID_SIZE - 1; int end_x = start_x + SCREEN_WIDTH / GRID_SIZE + 2; int start_y = player.offset_y / GRID_SIZE - 1; int end_y = start_y + SCREEN_HEIGHT/ GRID_SIZE + 2; for(y = start_y;y < end_y;y++){ for(x = start_x; x < end_x;x++){ SDL_Rect imageRect=(SDL_Rect){0, 0, IMAGE_WIDTH, IMAGE_HEIGHT}; SDL_Rect drawRect=(SDL_Rect){(x * GRID_SIZE) - player.offset_x, (y * GRID_SIZE) - player.offset_y, IMAGE_WIDTH*MAGNIFICATION, IMAGE_HEIGHT*MAGNIFICATION}; if ((x < 0) || (x > COL - 1) || (y < 0) || (y > ROW - 1)){ SDL_RenderCopy(renderer, mapchip[OUT_OF_MAP].map_image, &imageRect, &drawRect); } else { SDL_RenderCopy(renderer, mapchip[map_array[y*COL+x]].map_image, &imageRect, &drawRect); } } } return 0; } int load_move(SDL_Renderer *renderer) { char event_path[256] = {0}; sprintf(event_path, "data/%s.evt", MAP_EVENT_NAME); FILE *fp; char event[256] = {0}; int event_point_x; int event_point_y; DIRECTION direction_of_penetration; char buf[256] = {0}; char new_map_name[256] = {0}; char map_path[256] = {0}; int new_x; int new_y; int i = 0; fp = fopen(event_path, "r"); if (fp == NULL) { printf("file open error. %d\n", __LINE__); return 1; } for(i = 0;fgets(buf, sizeof(buf), fp) != NULL;i++) { if (strncmp(buf, "#", 1) != 0){ if (strncmp(buf, "MOVE", 4) == 0) { sscanf(buf, "%[^,],%d,%d,%d,%[^,],%d,%d", event, &event_point_x, &event_point_y, &direction_of_penetration, new_map_name, &new_x, &new_y); if (player.map_x == event_point_x && player.map_y == event_point_y) { if (player.direction == direction_of_penetration) { load_se(); sprintf(MAP_EVENT_NAME, "%s", new_map_name); sprintf(map_path, "data/%s.map", new_map_name); load_map(map_path); load_bgm(); player.map_x = new_x; player.map_y = new_y; player.pixel_x = player.map_x * GRID_SIZE; player.pixel_y = player.map_y * GRID_SIZE; load_npc(renderer); fade_out(renderer); break; } } } } } fclose(fp); return 0; } int fade_out(SDL_Renderer *renderer) { SDL_Rect rectangle; SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); rectangle.x = 0; rectangle.y = 0; rectangle.w = SCREEN_WIDTH; rectangle.h = SCREEN_HEIGHT; int i = 200; int inverse_flg = 0; while(1) { SDL_SetRenderDrawColor(renderer, 0, 0, 0, i); SDL_RenderFillRect(renderer, &rectangle); SDL_RenderPresent(renderer); if (inverse_flg == 0 && i <= 255) { i = i + 5; SDL_Delay(80); } if (i == 255) { inverse_flg = 1; } if (inverse_flg == 1) { clac_offset(player.pixel_x, player.pixel_y, &player.offset_x, &player.offset_y); draw_map(renderer); player_animation(renderer); SDL_Delay(20); i = i - 5; if (i == 200) { break; } } } return 0; } int load_se(void) { char event_path[256] = {0}; sprintf(event_path, "data/%s.evt", MAP_EVENT_NAME); FILE *fp; char buf[256] = {0}; char event[256] = {0}; int event_point_x; int event_point_y; char se_name[256] = {0}; char se_path[256] = {0}; int i = 0; Mix_Chunk *wave = NULL; fp = fopen(event_path, "r"); if (fp == NULL) { printf("file open error. %d\n", __LINE__); return 1; } for(i = 0;fgets(buf, sizeof(buf), fp) != NULL;i++) { if (strncmp(buf, "#", 1) != 0){ if (strncmp(buf, "SE", 2) == 0) { sscanf(buf, "%[^,],%d,%d,%[^,]", event, &event_point_x, &event_point_y, &se_name); if (player.map_x == event_point_x && player.map_y == event_point_y) { sprintf(se_path, "music/se/%s", se_name); wave = Mix_LoadWAV(se_path); if (wave == NULL) { return 1; } Mix_VolumeChunk(wave, 10); if ( Mix_PlayChannel(-1, wave, 0) == -1 ) { return 1; } break; } } } } fclose(fp); return 0; } int sound_se(char *se_name) { Mix_Chunk *wave = NULL; char se_path[256] = {0}; sprintf(se_path, "music/se/%s", se_name); wave = Mix_LoadWAV(se_path); if (wave == NULL) { return 1; } Mix_VolumeChunk(wave, 10); if ( Mix_PlayChannel(-1, wave, 0) == -1 ) { return 1; } return 0; } int load_bgm(void) { char event_path[256] = {0}; sprintf(event_path, "data/%s.evt", MAP_EVENT_NAME); FILE *fp; char buf[256] = {0}; char event[256] = {0}; char bgm_name[256] = {0}; char bgm_path[256] = {0}; int i = 0; fp = fopen(event_path, "r"); if (fp == NULL) { printf("file open error. %d\n", __LINE__); return 1; } for(i = 0;fgets(buf, sizeof(buf), fp) != NULL;i++) { if (strncmp(buf, "#", 1) != 0){ if (strncmp(buf, "BGM", 3) == 0) { sscanf(buf, "%[^,],%[^,]", event, &bgm_name); sprintf(bgm_path, "music/bgm/%s", bgm_name); music = Mix_LoadMUS(bgm_path); if (music == NULL) { return 1; } if (Mix_PlayMusic(music, -1) == -1) { return 1; } break; } } } fclose(fp); return 0; } int load_map(char *map_name) { FILE *fp; int i = 0; if ((fp = fopen(map_name, "rb")) == NULL) { return 1; } fread(&COL, sizeof(int), 1, fp); fread(&ROW, sizeof(int), 1, fp); fread(&OUT_OF_MAP, sizeof(int), 1, fp); map_array = realloc(map_array, sizeof(int) * COL * ROW); while(!feof(fp)) { fread(&map_array[i++], sizeof(int), 1, fp); } fclose(fp); return 0; } int load_mapchip(SDL_Renderer *renderer) { FILE *fp; int x, y, z; char n[256] = {0}; char path[256] = {0}; char buf[256] = {0}; int i = 0; fp = fopen("data/mapchip.dat", "r"); if (fp == NULL) { printf("file open error. %d\n", __LINE__); return 1; } for(i = 0;fgets(buf, sizeof(buf), fp) != NULL;i++){ sscanf(buf, "%d,%[^,],%d,%d", &x, n, &y, &z); mapchip[i].mapchip_id = x; strcpy(mapchip[i].mapchip_name, n); mapchip[i].movable = y; mapchip[i].change_locate = z; sprintf(path, "image/mapchip/%s.bmp", mapchip[i].mapchip_name); load_image(renderer, &mapchip[i].map_image, path); } number_of_map_image = i - 1; fclose(fp); return 0; } int is_movable(int x, int y) { int i; for(i = 0;i < number_of_npc_image;i++) { if (npc[i].npc.map_x == x && npc[i].npc.map_y == y) { return 1; } } if ( x < 0 || x > COL - 1 || y < 0 || y > ROW - 1) { return 2; } if (mapchip[map_array[y*COL+x]].movable == 1) { return 2; } if(player.map_x == x && player.map_y == y) { return 1; } return 0; } int clac_offset(int x, int y, int *offset_x, int *offset_y) { *offset_x = x - (SCREEN_WIDTH / 2); *offset_y = y - (SCREEN_HEIGHT / 2); return 0; } int make_box(SDL_Renderer *renderer, int x, int y, int w, int h, int blend, int r, int g, int b) { SDL_Rect rectangle; SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); rectangle.x = x; rectangle.y = y; rectangle.w = w; rectangle.h = h; SDL_SetRenderDrawColor(renderer, r, g, b, blend); SDL_RenderFillRect(renderer, &rectangle); SDL_RenderPresent(renderer); return 0; } int make_triangle(SDL_Renderer *renderer, int x1, int y1, int x2, int y2, int x3, int y3, int blend, int r, int g, int b) { SDL_Point points[4] = {{x1, y1},{x2, y2}, {x3, y3}, {x1, y1}}; SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(renderer, r, g, b, blend); SDL_RenderDrawLines(renderer, points, 4); SDL_RenderPresent(renderer); return 0; } int make_window(SDL_Renderer *renderer, WINDOW window) { int edge_size = 4; SDL_Rect rectangle; SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); rectangle.x = window.rectangle_x; rectangle.y = window.rectangle_y; rectangle.w = window.rectangle_w; rectangle.h = window.rectangle_h; // WHITE SDL_SetRenderDrawColor(renderer, 255, 255, 255, window.blend); SDL_RenderFillRect(renderer, &rectangle); rectangle.x = window.rectangle_x + edge_size; rectangle.y = window.rectangle_y + edge_size; rectangle.w = window.rectangle_w - edge_size * 2; rectangle.h = window.rectangle_h - edge_size * 2; // BLACK SDL_SetRenderDrawColor(renderer, 0, 0, 0, window.blend); SDL_RenderFillRect(renderer, &rectangle); return 0; } int window_engine(SDL_Renderer *renderer, WINDOW window) { make_window(renderer, window); return 0; } int window_update(SDL_Renderer *renderer, TTF_Font *font, SDL_Event e) { if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_SPACE){ if (message_window.visible == OUT_VISIBLE) { message_window.visible = IN_VISIBLE; } else { message_window.visible = OUT_VISIBLE; } } if (message_window.visible == IN_VISIBLE) { window_engine(renderer, message_window); message_engine(renderer, font, e); } return 0; } int message_engine(SDL_Renderer *renderer, TTF_Font *font, SDL_Event e) { int x; int message_length; int remaining_message_length; int word_length = 0; int byte_counter; int byt; int loop_counter = 0; int row_position = 0; int col_position = 0; int tmp_position = 0; const int row_size = 21; const int col_size = 84; const double start_x = 153.0; const double start_y = 354.0; const double inter_char = 5.35; char message_tmp[1024]; char message_out[100]; char word[6]; char isspace[3] = {0}; char isasterisk[3] = {0}; if (state == OFF) { get_character_message(e, &message); state = ON; } else { sound_se("conversation.ogg"); strcpy(message_tmp, message); message_length = strlen(message); while (*message != '\0') { SDL_Delay(60); byt = u8mb(*message); message += u8mb(*message); remaining_message_length = strlen(message); memset(word, '\0', 6); byte_counter = 0; for (x=word_length;x<message_length - remaining_message_length;x++) { sprintf(&word[byte_counter], "%c", message_tmp[x]); byte_counter++; } // 改行判定(半角スペースが2つ続いたら改行とみなす) if (*word == ' ') { if (strcmp(isspace, " ") == 0) { memset(isspace, '\0', 3); } strncat(isspace, word, 1); } else { memset(isspace, '\0', 3); } if (strcmp(isspace, " ") == 0) { if (loop_counter % col_size != 0) { loop_counter = loop_counter + (row_size - (loop_counter % row_size) - 1); } } // 改ページ判定(アスタリスクが2つ続いたら改ページとみなす) if (*word == '*') { if (strcmp(isasterisk, "**") == 0) { memset(isasterisk, '\0', 3); } strncat(isasterisk, word, 1); strncpy(word, " ", 1); } else { memset(isasterisk, '\0', 3); } if (strcmp(isasterisk, "**") == 0) { loop_counter = loop_counter + (col_size - (loop_counter % col_size) - 1); } // 折り返し判定 if (loop_counter != 0 && loop_counter % row_size == 0) { row_position += row_size; col_position = 0; tmp_position = word_length; if (loop_counter % col_size == 0 ) { while (1) { flash_triangle(renderer); if ( SDL_PollEvent(&e) ) { if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_SPACE){ sound_se("conversation.ogg"); break; } } } make_window(renderer, message_window); row_position = 0; col_position = 0; } else { sound_se("conversation.ogg"); } } else if (loop_counter % row_size > 0) { col_position = word_length - tmp_position; } display_character_string(renderer, font, word, start_x + col_position * inter_char, start_y + row_position); word_length = message_length - remaining_message_length; if ( SDL_PollEvent(&e) ) { if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_SPACE) { col_position = col_position + byt; loop_counter++; memset(message_out, '\0', 100); while (*message != '\0') { byt = u8mb(*message); message += u8mb(*message); remaining_message_length = strlen(message); memset(word, '\0', 6); byte_counter = 0; for (x=word_length;x<message_length - remaining_message_length;x++) { sprintf(&word[byte_counter], "%c", message_tmp[x]); byte_counter++; } // 改行判定(半角スペースが2つ続いたら改行とみなす) if (*word == ' ') { if (strcmp(isspace, " ") == 0) { memset(isspace, '\0', 3); } strncat(isspace, word, 1); } else { memset(isspace, '\0', 3); } if (strcmp(isspace, " ") == 0) { if (loop_counter % col_size != 0) { loop_counter = loop_counter + (row_size - (loop_counter % row_size) - 1); } } // 改ページ判定(アスタリスクが2つ続いたら改ページとみなす) if (*word == '*') { if (strcmp(isasterisk, "**") == 0) { memset(isasterisk, '\0', 3); } strncat(isasterisk, word, 1); strncpy(word, " ", 1); } else { memset(isasterisk, '\0', 3); } if (strcmp(isasterisk, "**") == 0) { loop_counter = loop_counter + (col_size - (loop_counter % col_size) - 1); } // 折り返し判定 if (loop_counter % row_size == 0) { display_character_string(renderer, font, message_out, start_x + col_position * inter_char, start_y + row_position); row_position += row_size; col_position = 0; tmp_position = word_length; memset(message_out, '\0', 100); if (loop_counter % col_size == 0 ) { while (1) { flash_triangle(renderer); if ( SDL_PollEvent(&e) ) { if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_SPACE){ break; } } } make_window(renderer, message_window); row_position = 0; col_position = 0; } } strcat(message_out, word); word_length = message_length - remaining_message_length; loop_counter++; } display_character_string(renderer, font, message_out, start_x + col_position * inter_char, start_y + row_position); break; } } loop_counter++; } if (message_window.visible == IN_VISIBLE) { while (1) { flash_triangle(renderer); if ( SDL_PollEvent(&e) ) { if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_SPACE){ message_window.visible = OUT_VISIBLE; break; } } } } state = OFF; } return 0; } int get_character_message(SDL_Event e, char **message) { int i; *message = "そっちには だれも いないよ!"; if (player.direction == UP) { if (is_movable(player.map_x, player.map_y - 1) == 1) { for(i = 0;i < number_of_npc_image;i++) { if (npc[i].npc.map_x == player.map_x && npc[i].npc.map_y == player.map_y - 1) { npc[i].npc.direction = DOWN; *message = npc[i].message; break; } } } } else if (player.direction == DOWN) { if (is_movable(player.map_x, player.map_y + 1) == 1) { for(i = 0;i < number_of_npc_image;i++) { if (npc[i].npc.map_x == player.map_x && npc[i].npc.map_y == player.map_y + 1) { npc[i].npc.direction = UP; *message = npc[i].message; break; } } } } else if (player.direction == RIGHT) { if (is_movable(player.map_x + 1, player.map_y) == 1) { for(i = 0;i < number_of_npc_image;i++) { if (npc[i].npc.map_x == player.map_x + 1 && npc[i].npc.map_y == player.map_y) { npc[i].npc.direction = LEFT; *message = npc[i].message; break; } } } } else if (player.direction == LEFT) { if (is_movable(player.map_x - 1, player.map_y) == 1) { for(i = 0;i < number_of_npc_image;i++) { if (npc[i].npc.map_x == player.map_x - 1 && npc[i].npc.map_y == player.map_y) { npc[i].npc.direction = RIGHT; *message = npc[i].message; break; } } } } return 0; } int display_character_string(SDL_Renderer *renderer, TTF_Font *font, char *string, double x, double y) { SDL_Surface *surface; SDL_Texture *texture; surface = TTF_RenderUTF8_Blended(font, string, (SDL_Color){255,255,255,255}); texture = SDL_CreateTextureFromSurface(renderer, surface); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); int iw,ih; SDL_QueryTexture(texture, NULL, NULL, &iw, &ih); SDL_Rect txtRect=(SDL_Rect){0,0,iw,ih}; SDL_Rect pasteRect=(SDL_Rect){x,y,iw,ih}; SDL_RenderCopy(renderer, texture, &txtRect, &pasteRect); SDL_RenderPresent(renderer); SDL_FreeSurface(surface); return 0; } // UTF-8文字の1バイト目を判定して文字のバイト数を返す関数 int u8mb(const char chr) { int byt = 1; if ((chr & 0x80) == 0x00) { //1byte文字は何もしない([byt = 1]のまま) } else if ((chr & 0xE0) == 0xC0) { //2byte文字 byt = 2; } else if ((chr & 0xF0) == 0xE0) { //3byte文字 byt = 3; } else if ((chr & 0xF8) == 0xF0) { //4byte文字 byt = 4; } else if ((chr & 0xFC) == 0xF8) { //5byte文字 byt = 5; } else if ((chr & 0xFE) == 0xFC) { //6byte文字 byt = 6; } return byt; } int flash_triangle(SDL_Renderer *renderer) { SDL_Delay(400); if (state == ON) { make_triangle(renderer, 310, 450, 320, 450, 315, 460, 255, 255, 255, 255); state = OFF; } else { make_box(renderer, 310, 442, 14, 22, 255, 0, 0, 0); state = ON; } return 0; }
実行結果
動画を貼り付ける方法がわからないので実際に音が出ているところをお聞かせできないが、ちゃんと音が出るようなった。
終わりに
BGM、SEが付くとぐっとゲームらしさが出てくる。この調子で、少しずつでも完成に近づけていきたいと思う。
SDL2でRPGゲームを作る 〜第15回 メッセージボックスの作成 その4〜
前回は、三角形の点滅を作った。今回は、メッセージのウィンドウ内での折り返し、改行、改ページを作っていこうと思う。
4回に渡ってメッセージボックスを作ってきたがとりあえずのところはここまでで、ひと区切り完成と言うことにしよう思う。
では、早速作っていこう。
折り返し処理
折り返し処理とはどんな動きかを考えると以下の感じになると思う。
1. メッセージがメッセージボックスの右端まで到達する
2. 次の文字の表示位置のX座標をメッセージボックスの左端に戻す
3. Y座標は1文字分下に移動する
4. もし、4行表示して折り返す場合次のページに移行する
5. 以上を繰り返す
これを踏まえて、順番に実装していこうと思う。
まず、使用している変数の意味から。
- loop_counter
- 現在表示している文字の総数
- row_position
- 行位置
- row_size
- 1行の文字数
- col_position
- 列位置
- col_size
- 1ページの文字数
では、変数の意味を定義したところで実装していく。
まず、メッセージがメッセージボックスの右端まで到達したことを判定する。
今回作っているメッセージボックスと文字のサイズだと1行の文字数は21文字になる。文字はループ処理を使って1文字ずつ表示しているのでちょうど21ループすると右端まで到達したことになる。
よって、loop_counter
なる変数を用意して、21(row_size
と同じなのでこれを使っている)で割り切れる数になったら次の行に進めることにした。
次に、次の文字の表示位置のX座標をメッセージボックスの左端に戻すためにはdisplay_character_string
関数の第4引数を初期位置に戻す必要があるのでcol_position
を0
に設定する。
さらに、Y座標を1文字分下に移動するために、row_position
にrow_size
を加算する。
ここで、もし、4行表示して折り返す場合だったとき、つまりloop_counter
がcol_size
で割り切れるときはrow_position
とcol_position
を0
に設定して次のページに移行する。これで、ひとまず折り返し処理は実装できた。以下が、折り返し処理のメインの部分を抜粋したコードになる。
while (*message != '\0') { // 途中省略 // 折り返し判定 if (loop_counter != 0 && loop_counter % row_size == 0) { row_position += row_size; col_position = 0; tmp_position = word_length; if (loop_counter % col_size == 0 ) { while (1) { flash_triangle(renderer); if ( SDL_PollEvent(&e) ) { if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_SPACE){ break; } } } make_window(renderer, message_window); row_position = 0; col_position = 0; } } else if (loop_counter % row_size > 0) { col_position = word_length - tmp_position; } display_character_string(renderer, font, word, start_x + col_position * inter_char, start_y + row_position); word_length = message_length - remaining_message_length; // 途中省略 loop_counter++; }
改行処理
続いて改行処理を考える。改行は半角スペースが2回続いたら改行ということにした。
実装するために考えたことは2つ。
1. 半角スペースが2回続いたことの判定方法
2. 判定できた時に実際にどうすれば改行になるか
まず、半角スペースが2回続いたことの判定方法だがこれは、単純に判定用の配列を用意して比較することで判定を行った。
次に、実際どうすれば改行になるかだがloop_counter
を1行の残りの文字数分加算することで折り返しが起きるようにした。以下が、実装になる。
char isspace[3] = {0}; if (*word == ' ') { if (strcmp(isspace, " ") == 0) { memset(isspace, '\0', 3); } strncat(isspace, word, 1); } else { memset(isspace, '\0', 3); } if (strcmp(isspace, " ") == 0) { if (loop_counter % col_size != 0) { loop_counter = loop_counter + (row_size - (loop_counter % row_size) - 1); } }
改ページ処理
続いて改ページ処理を考える。改ページはアスタリスクが2回続いたら改ページということにした。
実装方法はほぼ改行と同じ。
1. アスタリスクが2回続いたことの判定方法
2. 判定できた時に実際にどうすれば改行になるか
アスタリスクが2回続いたことの判定は改行と同じく判定用の配列を用意して比較することで判定を行った。
次に改ページだがこれも改行と同じ考えで、loop_counter
を1ページの残りの文字数分加算することで改ページができるようにした。
以下が、実装になる。
char isasterisk[3] = {0}; if (*word == '*') { if (strcmp(isasterisk, "**") == 0) { memset(isasterisk, '\0', 3); } strncat(isasterisk, word, 1); strncpy(word, " ", 1); } else { memset(isasterisk, '\0', 3); } if (strcmp(isasterisk, "**") == 0) { loop_counter = loop_counter + (col_size - (loop_counter % col_size) - 1); }
一気に文字を表示する
折り返し、改行、改ページと作成してついでにメッセージ表示中にスペースを押したら一気に文字を表示する
といった機能も実装したこれは考え方は上記の折り返し、改行、改ページと同じだが、出力の仕方が1文字ずつかそのページに出力する文字全てかと言った辺りで差がある。
message_out
という配列に文字をためていって改行のタイミングで表示させるようにしている。
以下に抜粋したコードを載せる。全容は、いつものごとく全コード表示するのでそちらで確認してほしい。
if (loop_counter % row_size == 0) { display_character_string(renderer, font, message_out, start_x + col_position * inter_char, start_y + row_position); row_position += row_size; col_position = 0; tmp_position = word_length; memset(message_out, '\0', 100); if (loop_counter % col_size == 0 ) { while (1) { flash_triangle(renderer); if ( SDL_PollEvent(&e) ) { if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_SPACE){ break; } } } make_window(renderer, message_window); row_position = 0; col_position = 0; } } strcat(message_out, word);
全コード表示
では、全コードを記載する。
window.h
#ifdef _RPGINC #else #include <stdio.h> #include <SDL2/SDL.h> #include <SDL2/SDL_image.h> #include <SDL2/SDL_ttf.h> typedef enum {DOWN, LEFT, RIGHT, UP} DIRECTION; typedef enum {FALSE, TRUE} MOVING; typedef enum {OUT_VISIBLE, IN_VISIBLE} VISIBLE; typedef enum {OFF, ON} STATE; typedef struct { int map_x; int map_y; int pixel_x; int pixel_y; int offset_x; int offset_y; int velocity_x; int velocity_y; DIRECTION direction; MOVING moving; } CARACTER; typedef struct { CARACTER npc; char message[1024]; SDL_Texture *npc_image; MOVING npc_move; } NPC; typedef struct { int mapchip_id; char mapchip_name[256]; int movable; int change_locate; SDL_Texture *map_image; } MAPCHIP; typedef struct { int rectangle_x; int rectangle_y; int rectangle_w; int rectangle_h; int blend; VISIBLE visible; } WINDOW; int clac_offset(int, int, int *, int *); int load_image(SDL_Renderer *, SDL_Texture **, char *); int player_animation(SDL_Renderer *); int player_update(SDL_Renderer *, SDL_Event); int player_move(SDL_Event); int load_npc(SDL_Renderer *); int npc_animation(SDL_Renderer *); int npc_update(SDL_Renderer *renderer, int); int npc_move(DIRECTION, int); int load_map_image(SDL_Renderer *, SDL_Texture **); int load_mapchip(SDL_Renderer *); int load_move(SDL_Renderer *); int load_map(char *); int draw_map(SDL_Renderer *); int is_movable(int, int); int fade_out(SDL_Renderer *); int make_window(SDL_Renderer *, WINDOW); int make_box(SDL_Renderer *, int, int, int, int, int, int, int, int); int make_triangle(SDL_Renderer *, int, int, int, int, int, int, int, int, int, int); int window_update(SDL_Renderer *, TTF_Font *, SDL_Event); int window_engine(SDL_Renderer *, WINDOW); int message_engine(SDL_Renderer *, TTF_Font *, SDL_Event); int display_character_string(SDL_Renderer *, TTF_Font *, char *, double, double); int get_character_message(SDL_Event, char **); int u8mb(const char); int flash_triangle(SDL_Renderer *); #endif
window.c
#include <stdio.h> #include <stdlib.h> #include <time.h> #include <SDL2/SDL.h> #include <SDL2/SDL_image.h> #include <SDL2/SDL_ttf.h> #include "RPGInC.h" #define FONT_PATH "font/PixelMplus12-Regular.ttf" const int SCREEN_WIDTH = 640; const int SCREEN_HEIGHT = 480; const int IMAGE_WIDTH = 16; const int IMAGE_HEIGHT = 16; const int MAGNIFICATION = 2; const int GRID_SIZE = 32; const int FONT_SIZE = 16; int ROW = 15; int COL = 20; int OUT_OF_MAP = 0; char MAP_EVENT_NAME[256] = "field"; int animecycle = 24; int speed = 2; int frame = 0; int number_of_map_image = 0; int number_of_npc_image = 0; CARACTER player = {1, 1, 32, 32, 0, 0, 0, 0, DOWN, FALSE}; NPC npc[256] = {0}; MAPCHIP mapchip[256] = {0}; int *map_array; WINDOW message_window = {140, 334, 360, 140, 255, OUT_VISIBLE}; char *message; STATE state = OFF; int main (int argc, char *argv[]) { SDL_Window *window = NULL; SDL_Renderer *renderer = NULL; TTF_Font *font = NULL; //Initialize SDL if( SDL_Init( SDL_INIT_VIDEO ) < 0 ) { printf( "SDL could not initialize! SDL_Error: %s\n", SDL_GetError() ); return 1; } window = SDL_CreateWindow( "DRAW IMAGE TEST", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN ); if( window == NULL ) { printf( "Window could not be created! SDL_Error: %s\n", SDL_GetError() ); return 1; } else { renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); } //Initialize TTF if ( TTF_Init() < 0 ) { printf("TTFcould not initialize! TTF_Error: %s\n", TTF_GetError()); } font = TTF_OpenFont(FONT_PATH, FONT_SIZE); if ( font == NULL ) { printf("TTF_OpenFont: %s\n", TTF_GetError()); } load_mapchip(renderer); load_map("data/field.map"); load_npc(renderer); // main loop while (1) { SDL_Delay(5); SDL_Event e; clac_offset(player.pixel_x, player.pixel_y, &player.offset_x, &player.offset_y); SDL_RenderClear(renderer); draw_map(renderer); npc_animation(renderer); player_animation(renderer); player_update(renderer, e); window_update(renderer, font, e); SDL_RenderPresent(renderer); // event handling if ( SDL_PollEvent(&e) ) { if (e.type == SDL_QUIT){ break; } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE){ break; } } } IMG_Quit(); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); int i; for (i = 0;i < number_of_map_image;i++) { SDL_DestroyTexture(mapchip[i].map_image); } for (i = 0;i < number_of_npc_image;i++) { SDL_DestroyTexture(npc[i].npc_image); } SDL_Quit(); return 0; } int load_image(SDL_Renderer *renderer, SDL_Texture **image_texture, char *filename) { SDL_Surface *image = NULL; // 画像の読み込み image = IMG_Load(filename); if(!image) { printf("IMG_Load: %s\n", IMG_GetError()); return 1; } // 透過色の設定 SDL_SetColorKey(image, SDL_TRUE, SDL_MapRGB(image->format, 255, 0, 255)); *image_texture = SDL_CreateTextureFromSurface(renderer, image); SDL_FreeSurface(image); return 0; } int player_animation(SDL_Renderer *renderer) { SDL_Texture *cat_image = NULL; load_image(renderer, &cat_image, "image/charachip/chiharu.bmp"); // load_image(renderer, &cat_image, "image/charachip/white_cat.bmp"); int x = ((frame / animecycle) % 4) * 16; int y = player.direction * IMAGE_HEIGHT; SDL_Rect imageRect=(SDL_Rect){x, y, IMAGE_WIDTH, IMAGE_HEIGHT}; SDL_Rect drawRect=(SDL_Rect){player.pixel_x - player.offset_x, player.pixel_y - player.offset_y, IMAGE_WIDTH*MAGNIFICATION, IMAGE_HEIGHT*MAGNIFICATION}; SDL_RenderCopy(renderer, cat_image, &imageRect, &drawRect); if (frame <= animecycle * 4) { frame += 1; } else{ frame = 0; } SDL_DestroyTexture(cat_image); return 0; } int player_update(SDL_Renderer *renderer, SDL_Event e) { if (player.moving == TRUE) { player.pixel_x = player.pixel_x + player.velocity_x; player.pixel_y = player.pixel_y + player.velocity_y; if (player.pixel_x % GRID_SIZE == 0 && player.pixel_y % GRID_SIZE == 0){ player.moving = FALSE; player.map_x = player.pixel_x / GRID_SIZE; player.map_y = player.pixel_y / GRID_SIZE; load_move(renderer); player_move(e); } } else { player_move(e); } } int player_move(SDL_Event e) { if (message_window.visible == OUT_VISIBLE) { if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_UP){ player.direction = UP; if (is_movable(player.map_x, player.map_y - 1) == 0) { player.velocity_x = 0; player.velocity_y = -speed; player.moving = TRUE; } } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_DOWN){ player.direction = DOWN; if (is_movable(player.map_x, player.map_y + 1) == 0) { player.velocity_x = 0; player.velocity_y = speed; player.moving = TRUE; } } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_RIGHT){ player.direction = RIGHT; if (is_movable(player.map_x + 1, player.map_y) == 0) { player.velocity_x = speed; player.velocity_y = 0; player.moving = TRUE; } } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_LEFT){ player.direction = LEFT; if (is_movable(player.map_x - 1, player.map_y) == 0) { player.velocity_x = -speed; player.velocity_y = 0; player.moving = TRUE; } } } return 0; } int load_npc(SDL_Renderer *renderer) { char event_path[256] = {0}; sprintf(event_path, "data/%s.evt", MAP_EVENT_NAME); FILE *fp; char event[256] = {0}; char npc_name[256] = {0}; int map_x; int map_y; DIRECTION direction; MOVING moving; int max_step; int message_length; char message[1024] = {0}; char buf[1024] = {0}; char npc_path[256] = {0}; int i = 0; int element_number = 0; fp = fopen(event_path, "r"); if (fp == NULL) { printf("file open error. %d\n", __LINE__); return 1; } for (i = 0;i < number_of_npc_image;i++) { npc[i].npc.map_x = 0; npc[i].npc.map_y = 0; npc[i].npc.pixel_x = 0; npc[i].npc.pixel_y = 0; npc[i].npc.direction = 0; npc[i].npc.moving = 0; SDL_DestroyTexture(npc[i].npc_image); sprintf(npc[i].message, "%s", '\0'); } for(i = 0;fgets(buf, sizeof(buf), fp) != NULL;i++) { if (strncmp(buf, "#", 1) != 0){ if (strncmp(buf, "CHARA", 5) == 0) { sscanf(buf, "%[^,],%[^,],%d,%d,%d,%d,%[^,]", event, npc_name, &map_x, &map_y, &direction, &moving, message); sprintf(npc_path, "image/charachip/%s.bmp", npc_name); load_image(renderer, &npc[element_number].npc_image, npc_path); npc[element_number].npc.map_x = map_x; npc[element_number].npc.map_y = map_y; npc[element_number].npc.pixel_x = map_x * GRID_SIZE; npc[element_number].npc.pixel_y = map_y * GRID_SIZE; npc[element_number].npc.direction = direction; npc[element_number].npc_move = moving; message_length = strlen(message); message[message_length - 1] = '\0'; sprintf(npc[element_number].message, "%s", message); element_number += 1; } } } number_of_npc_image = element_number; fclose(fp); return 0; } int npc_animation(SDL_Renderer *renderer) { int i; for(i = 0; number_of_npc_image >= i;i++) { int x = ((frame / animecycle) % 4) * 16; int y = npc[i].npc.direction * IMAGE_HEIGHT; SDL_Rect imageRect=(SDL_Rect){x/4, y/4, IMAGE_WIDTH/4, IMAGE_HEIGHT/4}; SDL_Rect drawRect=(SDL_Rect){npc[i].npc.pixel_x - player.offset_x, npc[i].npc.pixel_y - player.offset_y, IMAGE_WIDTH*MAGNIFICATION, IMAGE_HEIGHT*MAGNIFICATION}; SDL_RenderCopy(renderer, npc[i].npc_image, &imageRect, &drawRect); if (npc[i].npc_move == TRUE && number_of_npc_image != 0) { npc_update(renderer, i); } } return 0; } int npc_update(SDL_Renderer *renderer, int element) { srand((unsigned)time(NULL)); int target = rand()%number_of_npc_image; int action = rand()%10; if (npc[element].npc_move == TRUE) { if (npc[element].npc.moving == TRUE) { npc[element].npc.pixel_x = npc[element].npc.pixel_x + npc[element].npc.velocity_x; npc[element].npc.pixel_y = npc[element].npc.pixel_y + npc[element].npc.velocity_y; if (npc[element].npc.pixel_x % GRID_SIZE == 0 && npc[element].npc.pixel_y % GRID_SIZE == 0) { npc[element].npc.moving = FALSE; npc[element].npc.map_x = npc[element].npc.pixel_x / GRID_SIZE; npc[element].npc.map_y = npc[element].npc.pixel_y / GRID_SIZE; } } else { if (target == element && action < 3) { npc_move(rand()%4, element); } } } } int npc_move(DIRECTION direction, int element) { if (message_window.visible == OUT_VISIBLE) { if (frame == 0) { if (direction == UP){ npc[element].npc.direction = UP; if (is_movable(npc[element].npc.map_x, npc[element].npc.map_y - 1) == 0) { npc[element].npc.velocity_x = 0; npc[element].npc.velocity_y = -speed; npc[element].npc.moving = TRUE; } } else if (direction == DOWN){ npc[element].npc.direction = DOWN; if (is_movable(npc[element].npc.map_x, npc[element].npc.map_y + 1) == 0) { npc[element].npc.velocity_x = 0; npc[element].npc.velocity_y = speed; npc[element].npc.moving = TRUE; } } else if (direction == RIGHT){ npc[element].npc.direction = RIGHT; if (is_movable(npc[element].npc.map_x + 1, npc[element].npc.map_y) == 0) { npc[element].npc.velocity_x = speed; npc[element].npc.velocity_y = 0; npc[element].npc.moving = TRUE; } } else if (direction == LEFT){ npc[element].npc.direction = LEFT; if (is_movable(npc[element].npc.map_x - 1, npc[element].npc.map_y) == 0) { npc[element].npc.velocity_x = -speed; npc[element].npc.velocity_y = 0; npc[element].npc.moving = TRUE; } } } } return 0; } int draw_map(SDL_Renderer *renderer){ int x, y; int start_x = player.offset_x / GRID_SIZE - 1; int end_x = start_x + SCREEN_WIDTH / GRID_SIZE + 2; int start_y = player.offset_y / GRID_SIZE - 1; int end_y = start_y + SCREEN_HEIGHT/ GRID_SIZE + 2; for(y = start_y;y < end_y;y++){ for(x = start_x; x < end_x;x++){ SDL_Rect imageRect=(SDL_Rect){0, 0, IMAGE_WIDTH, IMAGE_HEIGHT}; SDL_Rect drawRect=(SDL_Rect){(x * GRID_SIZE) - player.offset_x, (y * GRID_SIZE) - player.offset_y, IMAGE_WIDTH*MAGNIFICATION, IMAGE_HEIGHT*MAGNIFICATION}; if ((x < 0) || (x > COL - 1) || (y < 0) || (y > ROW - 1)){ SDL_RenderCopy(renderer, mapchip[OUT_OF_MAP].map_image, &imageRect, &drawRect); } else { SDL_RenderCopy(renderer, mapchip[map_array[y*COL+x]].map_image, &imageRect, &drawRect); } } } return 0; } int load_move(SDL_Renderer *renderer) { char event_path[256] = {0}; sprintf(event_path, "data/%s.evt", MAP_EVENT_NAME); FILE *fp; char event[256] = {0}; int event_point_x; int event_point_y; DIRECTION direction_of_penetration; char buf[256] = {0}; char new_map_name[256] = {0}; char map_path[256] = {0}; int new_x; int new_y; int i = 0; fp = fopen(event_path, "r"); if (fp == NULL) { printf("file open error. %d\n", __LINE__); return 1; } for(i = 0;fgets(buf, sizeof(buf), fp) != NULL;i++) { if (strncmp(buf, "#", 1) != 0){ if (strncmp(buf, "MOVE", 4) == 0) { sscanf(buf, "%[^,],%d,%d,%d,%[^,],%d,%d", event, &event_point_x, &event_point_y, &direction_of_penetration, new_map_name, &new_x, &new_y); if (player.map_x == event_point_x && player.map_y == event_point_y) { if (player.direction == direction_of_penetration) { sprintf(MAP_EVENT_NAME, "%s", new_map_name); sprintf(map_path, "data/%s.map", new_map_name); load_map(map_path); player.map_x = new_x; player.map_y = new_y; player.pixel_x = player.map_x * GRID_SIZE; player.pixel_y = player.map_y * GRID_SIZE; load_npc(renderer); fade_out(renderer); break; } } } } } fclose(fp); return 0; } int fade_out(SDL_Renderer *renderer) { SDL_Rect rectangle; SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); rectangle.x = 0; rectangle.y = 0; rectangle.w = SCREEN_WIDTH; rectangle.h = SCREEN_HEIGHT; int i = 200; int inverse_flg = 0; while(1) { SDL_SetRenderDrawColor(renderer, 0, 0, 0, i); SDL_RenderFillRect(renderer, &rectangle); SDL_RenderPresent(renderer); if (inverse_flg == 0 && i <= 255) { i = i + 5; SDL_Delay(80); } if (i == 255) { inverse_flg = 1; } if (inverse_flg == 1) { clac_offset(player.pixel_x, player.pixel_y, &player.offset_x, &player.offset_y); draw_map(renderer); player_animation(renderer); SDL_Delay(20); i = i - 5; if (i == 200) { break; } } } return 0; } int load_map(char *map_name) { FILE *fp; int i = 0; if ((fp = fopen(map_name, "rb")) == NULL) { return 1; } fread(&COL, sizeof(int), 1, fp); fread(&ROW, sizeof(int), 1, fp); fread(&OUT_OF_MAP, sizeof(int), 1, fp); map_array = realloc(map_array, sizeof(int) * COL * ROW); while(!feof(fp)) { fread(&map_array[i++], sizeof(int), 1, fp); } fclose(fp); return 0; } int load_mapchip(SDL_Renderer *renderer) { FILE *fp; int x, y, z; char n[256] = {0}; char path[256] = {0}; char buf[256] = {0}; int i = 0; fp = fopen("data/mapchip.dat", "r"); if (fp == NULL) { printf("file open error. %d\n", __LINE__); return 1; } for(i = 0;fgets(buf, sizeof(buf), fp) != NULL;i++){ sscanf(buf, "%d,%[^,],%d,%d", &x, n, &y, &z); mapchip[i].mapchip_id = x; strcpy(mapchip[i].mapchip_name, n); mapchip[i].movable = y; mapchip[i].change_locate = z; sprintf(path, "image/mapchip/%s.bmp", mapchip[i].mapchip_name); load_image(renderer, &mapchip[i].map_image, path); } number_of_map_image = i - 1; fclose(fp); return 0; } int is_movable(int x, int y) { int i; for(i = 0;i < number_of_npc_image;i++) { if (npc[i].npc.map_x == x && npc[i].npc.map_y == y) { return 1; } } if ( x < 0 || x > COL - 1 || y < 0 || y > ROW - 1) { return 2; } if (mapchip[map_array[y*COL+x]].movable == 1) { return 2; } if(player.map_x == x && player.map_y == y) { return 1; } return 0; } int clac_offset(int x, int y, int *offset_x, int *offset_y) { *offset_x = x - (SCREEN_WIDTH / 2); *offset_y = y - (SCREEN_HEIGHT / 2); return 0; } int make_box(SDL_Renderer *renderer, int x, int y, int w, int h, int blend, int r, int g, int b) { SDL_Rect rectangle; SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); rectangle.x = x; rectangle.y = y; rectangle.w = w; rectangle.h = h; SDL_SetRenderDrawColor(renderer, r, g, b, blend); SDL_RenderFillRect(renderer, &rectangle); SDL_RenderPresent(renderer); return 0; } int make_triangle(SDL_Renderer *renderer, int x1, int y1, int x2, int y2, int x3, int y3, int blend, int r, int g, int b) { SDL_Point points[4] = {{x1, y1},{x2, y2}, {x3, y3}, {x1, y1}}; SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(renderer, r, g, b, blend); SDL_RenderDrawLines(renderer, points, 4); SDL_RenderPresent(renderer); return 0; } int make_window(SDL_Renderer *renderer, WINDOW window) { int edge_size = 4; SDL_Rect rectangle; SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); rectangle.x = window.rectangle_x; rectangle.y = window.rectangle_y; rectangle.w = window.rectangle_w; rectangle.h = window.rectangle_h; // WHITE SDL_SetRenderDrawColor(renderer, 255, 255, 255, window.blend); SDL_RenderFillRect(renderer, &rectangle); rectangle.x = window.rectangle_x + edge_size; rectangle.y = window.rectangle_y + edge_size; rectangle.w = window.rectangle_w - edge_size * 2; rectangle.h = window.rectangle_h - edge_size * 2; // BLACK SDL_SetRenderDrawColor(renderer, 0, 0, 0, window.blend); SDL_RenderFillRect(renderer, &rectangle); return 0; } int window_engine(SDL_Renderer *renderer, WINDOW window) { make_window(renderer, window); return 0; } int window_update(SDL_Renderer *renderer, TTF_Font *font, SDL_Event e) { if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_SPACE){ if (message_window.visible == OUT_VISIBLE) { message_window.visible = IN_VISIBLE; } else { message_window.visible = OUT_VISIBLE; } } if (message_window.visible == IN_VISIBLE) { window_engine(renderer, message_window); message_engine(renderer, font, e); } return 0; } int message_engine(SDL_Renderer *renderer, TTF_Font *font, SDL_Event e) { int x; int message_length; int remaining_message_length; int word_length = 0; int byte_counter; int byt; int loop_counter = 0; int row_position = 0; int col_position = 0; int tmp_position = 0; const int row_size = 21; const int col_size = 84; const double start_x = 153.0; const double start_y = 354.0; const double inter_char = 5.35; char message_tmp[1024]; char message_out[100]; char word[6]; char isspace[3] = {0}; char isasterisk[3] = {0}; if (state == OFF) { get_character_message(e, &message); state = ON; } else { strcpy(message_tmp, message); message_length = strlen(message); while (*message != '\0') { SDL_Delay(120); byt = u8mb(*message); message += u8mb(*message); remaining_message_length = strlen(message); memset(word, '\0', 6); byte_counter = 0; for (x=word_length;x<message_length - remaining_message_length;x++) { sprintf(&word[byte_counter], "%c", message_tmp[x]); byte_counter++; } // 改行判定(半角スペースが2つ続いたら改行とみなす) if (*word == ' ') { if (strcmp(isspace, " ") == 0) { memset(isspace, '\0', 3); } strncat(isspace, word, 1); } else { memset(isspace, '\0', 3); } if (strcmp(isspace, " ") == 0) { if (loop_counter % col_size != 0) { loop_counter = loop_counter + (row_size - (loop_counter % row_size) - 1); } } // 改ページ判定(アスタリスクが2つ続いたら改ページとみなす) if (*word == '*') { if (strcmp(isasterisk, "**") == 0) { memset(isasterisk, '\0', 3); } strncat(isasterisk, word, 1); strncpy(word, " ", 1); } else { memset(isasterisk, '\0', 3); } if (strcmp(isasterisk, "**") == 0) { loop_counter = loop_counter + (col_size - (loop_counter % col_size) - 1); } // 折り返し判定 if (loop_counter != 0 && loop_counter % row_size == 0) { row_position += row_size; col_position = 0; tmp_position = word_length; if (loop_counter % col_size == 0 ) { while (1) { flash_triangle(renderer); if ( SDL_PollEvent(&e) ) { if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_SPACE){ break; } } } make_window(renderer, message_window); row_position = 0; col_position = 0; } } else if (loop_counter % row_size > 0) { col_position = word_length - tmp_position; } display_character_string(renderer, font, word, start_x + col_position * inter_char, start_y + row_position); word_length = message_length - remaining_message_length; if ( SDL_PollEvent(&e) ) { if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_SPACE) { col_position = col_position + byt; loop_counter++; memset(message_out, '\0', 100); while (*message != '\0') { byt = u8mb(*message); message += u8mb(*message); remaining_message_length = strlen(message); memset(word, '\0', 6); byte_counter = 0; for (x=word_length;x<message_length - remaining_message_length;x++) { sprintf(&word[byte_counter], "%c", message_tmp[x]); byte_counter++; } // 改行判定(半角スペースが2つ続いたら改行とみなす) if (*word == ' ') { if (strcmp(isspace, " ") == 0) { memset(isspace, '\0', 3); } strncat(isspace, word, 1); } else { memset(isspace, '\0', 3); } if (strcmp(isspace, " ") == 0) { if (loop_counter % col_size != 0) { loop_counter = loop_counter + (row_size - (loop_counter % row_size) - 1); } } // 改ページ判定(アスタリスクが2つ続いたら改ページとみなす) if (*word == '*') { if (strcmp(isasterisk, "**") == 0) { memset(isasterisk, '\0', 3); } strncat(isasterisk, word, 1); strncpy(word, " ", 1); } else { memset(isasterisk, '\0', 3); } if (strcmp(isasterisk, "**") == 0) { loop_counter = loop_counter + (col_size - (loop_counter % col_size) - 1); } // 折り返し判定 if (loop_counter % row_size == 0) { display_character_string(renderer, font, message_out, start_x + col_position * inter_char, start_y + row_position); row_position += row_size; col_position = 0; tmp_position = word_length; memset(message_out, '\0', 100); if (loop_counter % col_size == 0 ) { while (1) { flash_triangle(renderer); if ( SDL_PollEvent(&e) ) { if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_SPACE){ break; } } } make_window(renderer, message_window); row_position = 0; col_position = 0; } } strcat(message_out, word); word_length = message_length - remaining_message_length; loop_counter++; } display_character_string(renderer, font, message_out, start_x + col_position * inter_char, start_y + row_position); break; } } loop_counter++; } if (message_window.visible == IN_VISIBLE) { while (1) { flash_triangle(renderer); if ( SDL_PollEvent(&e) ) { if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_SPACE){ message_window.visible = OUT_VISIBLE; break; } } } } state = OFF; } return 0; } int get_character_message(SDL_Event e, char **message) { int i; *message = "そっちには だれも いないよ!"; if (player.direction == UP) { if (is_movable(player.map_x, player.map_y - 1) == 1) { for(i = 0;i < number_of_npc_image;i++) { if (npc[i].npc.map_x == player.map_x && npc[i].npc.map_y == player.map_y - 1) { npc[i].npc.direction = DOWN; *message = npc[i].message; break; } } } } else if (player.direction == DOWN) { if (is_movable(player.map_x, player.map_y + 1) == 1) { for(i = 0;i < number_of_npc_image;i++) { if (npc[i].npc.map_x == player.map_x && npc[i].npc.map_y == player.map_y + 1) { npc[i].npc.direction = UP; *message = npc[i].message; break; } } } } else if (player.direction == RIGHT) { if (is_movable(player.map_x + 1, player.map_y) == 1) { for(i = 0;i < number_of_npc_image;i++) { if (npc[i].npc.map_x == player.map_x + 1 && npc[i].npc.map_y == player.map_y) { npc[i].npc.direction = LEFT; *message = npc[i].message; break; } } } } else if (player.direction == LEFT) { if (is_movable(player.map_x - 1, player.map_y) == 1) { for(i = 0;i < number_of_npc_image;i++) { if (npc[i].npc.map_x == player.map_x - 1 && npc[i].npc.map_y == player.map_y) { npc[i].npc.direction = RIGHT; *message = npc[i].message; break; } } } } return 0; } int display_character_string(SDL_Renderer *renderer, TTF_Font *font, char *string, double x, double y) { SDL_Surface *surface; SDL_Texture *texture; surface = TTF_RenderUTF8_Blended(font, string, (SDL_Color){255,255,255,255}); texture = SDL_CreateTextureFromSurface(renderer, surface); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); int iw,ih; SDL_QueryTexture(texture, NULL, NULL, &iw, &ih); SDL_Rect txtRect=(SDL_Rect){0,0,iw,ih}; SDL_Rect pasteRect=(SDL_Rect){x,y,iw,ih}; SDL_RenderCopy(renderer, texture, &txtRect, &pasteRect); SDL_RenderPresent(renderer); SDL_FreeSurface(surface); return 0; } // UTF-8文字の1バイト目を判定して文字のバイト数を返す関数 int u8mb(const char chr) { int byt = 1; if ((chr & 0x80) == 0x00) { //1byte文字は何もしない([byt = 1]のまま) } else if ((chr & 0xE0) == 0xC0) { //2byte文字 byt = 2; } else if ((chr & 0xF0) == 0xE0) { //3byte文字 byt = 3; } else if ((chr & 0xF8) == 0xF0) { //4byte文字 byt = 4; } else if ((chr & 0xFC) == 0xF8) { //5byte文字 byt = 5; } else if ((chr & 0xFE) == 0xFC) { //6byte文字 byt = 6; } return byt; } int flash_triangle(SDL_Renderer *renderer) { SDL_Delay(400); if (state == ON) { make_triangle(renderer, 310, 450, 320, 450, 315, 460, 255, 255, 255, 255); state = OFF; } else { make_box(renderer, 310, 442, 14, 22, 255, 0, 0, 0); state = ON; } return 0; }
実行結果
実行結果はこんな感じ。折り返し、改行、改ページ、さらには、一気に表示すると言った機能が実装できた。
終わりに
4回に渡ってメッセージボックスを作ってきたがひとまずこれで完成ということにしてよいかなぁといったところまでつくることができた。今後、追加したい処理が出てきたら適に追加しようと思う。次回からはBGMやSEといった音を入れる処理を作っていきたい。
SDL2でRPGゲームを作る 〜第14回 メッセージボックスの作成 その3〜
前回は、流れるメッセージを作った。今回は、メッセージのウィンドウ内での折り返し、改行、改ページを作っていこうと思ったけど、ちょっと横道にそれて、メッセージボックが改ページの時にでる三角形の点滅処理を作ろうと思う。
では、早速作っていこう。
三角形を点滅させる
三角形を点滅させるには表示・非表示を一定間隔で繰り返せば良い。
三角形を表示させるために以下の関数を用意した。
int make_triangle(SDL_Renderer *renderer, int x1, int y1, int x2, int y2, int x3, int y3, int blend, int r, int g, int b) { SDL_Point points[4] = {{x1, y1},{x2, y2}, {x3, y3}, {x1, y1}}; SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(renderer, r, g, b, blend); SDL_RenderDrawLines(renderer, points, 4); SDL_RenderPresent(renderer); return 0; }
で、非表示はどうするかというと四角形を上から重ねて隠すようにする。
三角形を消すための四角形を作る関数は以下。
int make_box(SDL_Renderer *renderer, int x, int y, int w, int h, int blend, int r, int g, int b) { SDL_Rect rectangle; SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); rectangle.x = x; rectangle.y = y; rectangle.w = w; rectangle.h = h; SDL_SetRenderDrawColor(renderer, r, g, b, blend); SDL_RenderFillRect(renderer, &rectangle); SDL_RenderPresent(renderer); return 0; }
これを交互に表示すると三角形の点滅処理になる。
点滅処理はこんな感じ。
if (message_window.visible == IN_VISIBLE) { while (1) { SDL_Delay(400); if (state == ON) { make_triangle(renderer, 310, 450, 320, 450, 315, 460, 255, 255, 255, 255); state = OFF; } else { make_box(renderer, 310, 442, 14, 22, 255, 0, 0, 0); state = ON; } if ( SDL_PollEvent(&e) ) { if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_SPACE){ message_window.visible = OUT_VISIBLE; break; } } } }
全コード表示
では、全コードを表示する。
window.h
#ifdef _RPGINC #else #include <stdio.h> #include <SDL2/SDL.h> #include <SDL2/SDL_image.h> #include <SDL2/SDL_ttf.h> typedef enum {DOWN, LEFT, RIGHT, UP} DIRECTION; typedef enum {FALSE, TRUE} MOVING; typedef enum {OUT_VISIBLE, IN_VISIBLE} VISIBLE; typedef enum {OFF, ON} STATE; typedef struct { int map_x; int map_y; int pixel_x; int pixel_y; int offset_x; int offset_y; int velocity_x; int velocity_y; DIRECTION direction; MOVING moving; } CARACTER; typedef struct { CARACTER npc; char message[1024]; SDL_Texture *npc_image; MOVING npc_move; } NPC; typedef struct { int mapchip_id; char mapchip_name[256]; int movable; int change_locate; SDL_Texture *map_image; } MAPCHIP; typedef struct { int rectangle_x; int rectangle_y; int rectangle_w; int rectangle_h; int blend; VISIBLE visible; } WINDOW; int clac_offset(int, int, int *, int *); int load_image(SDL_Renderer *, SDL_Texture **, char *); int player_animation(SDL_Renderer *); int player_update(SDL_Renderer *, SDL_Event); int player_move(SDL_Event); int load_npc(SDL_Renderer *); int npc_animation(SDL_Renderer *); int npc_update(SDL_Renderer *renderer, int); int npc_move(DIRECTION, int); int load_map_image(SDL_Renderer *, SDL_Texture **); int load_mapchip(SDL_Renderer *); int load_move(SDL_Renderer *); int load_map(char *); int draw_map(SDL_Renderer *); int is_movable(int, int); int fade_out(SDL_Renderer *); int make_window(SDL_Renderer *, WINDOW); int make_box(SDL_Renderer *, int, int, int, int, int, int, int, int); int make_triangle(SDL_Renderer *, int, int, int, int, int, int, int, int, int, int); int window_update(SDL_Renderer *, TTF_Font *, SDL_Event); int window_engine(SDL_Renderer *, WINDOW); int message_engine(SDL_Renderer *, TTF_Font *, SDL_Event); int display_character_string(SDL_Renderer *, TTF_Font *, char *, int, int); int get_character_message(SDL_Event, char **); int u8mb(const char); #endif
window.c
#include <stdio.h> #include <stdlib.h> #include <time.h> #include <SDL2/SDL.h> #include <SDL2/SDL_image.h> #include <SDL2/SDL_ttf.h> #include "window.h" #define FONT_PATH "font/PixelMplus12-Regular.ttf" const int SCREEN_WIDTH = 640; const int SCREEN_HEIGHT = 480; const int IMAGE_WIDTH = 16; const int IMAGE_HEIGHT = 16; const int MAGNIFICATION = 2; const int GRID_SIZE = 32; const int FONT_SIZE = 16; int ROW = 15; int COL = 20; int OUT_OF_MAP = 0; char MAP_EVENT_NAME[256] = "field"; int animecycle = 24; int speed = 2; int frame = 0; int number_of_map_image = 0; int number_of_npc_image = 0; CARACTER player = {1, 1, 32, 32, 0, 0, 0, 0, DOWN, FALSE}; NPC npc[256] = {0}; MAPCHIP mapchip[256] = {0}; int *map_array; WINDOW message_window = {140, 334, 360, 140, 255, OUT_VISIBLE}; char *message; STATE state = OFF; int main (int argc, char *argv[]) { SDL_Window *window = NULL; SDL_Renderer *renderer = NULL; TTF_Font *font = NULL; //Initialize SDL if( SDL_Init( SDL_INIT_VIDEO ) < 0 ) { printf( "SDL could not initialize! SDL_Error: %s\n", SDL_GetError() ); return 1; } window = SDL_CreateWindow( "DRAW IMAGE TEST", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN ); if( window == NULL ) { printf( "Window could not be created! SDL_Error: %s\n", SDL_GetError() ); return 1; } else { renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); } //Initialize TTF if ( TTF_Init() < 0 ) { printf("TTFcould not initialize! TTF_Error: %s\n", TTF_GetError()); } font = TTF_OpenFont(FONT_PATH, FONT_SIZE); if ( font == NULL ) { printf("TTF_OpenFont: %s\n", TTF_GetError()); } load_mapchip(renderer); load_map("data/field.map"); load_npc(renderer); // main loop while (1) { SDL_Delay(5); SDL_Event e; clac_offset(player.pixel_x, player.pixel_y, &player.offset_x, &player.offset_y); SDL_RenderClear(renderer); draw_map(renderer); npc_animation(renderer); player_animation(renderer); player_update(renderer, e); window_update(renderer, font, e); SDL_RenderPresent(renderer); // event handling if ( SDL_PollEvent(&e) ) { if (e.type == SDL_QUIT){ break; } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE){ break; } } } IMG_Quit(); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); int i; for (i = 0;i < number_of_map_image;i++) { SDL_DestroyTexture(mapchip[i].map_image); } for (i = 0;i < number_of_npc_image;i++) { SDL_DestroyTexture(npc[i].npc_image); } SDL_Quit(); return 0; } int load_image(SDL_Renderer *renderer, SDL_Texture **image_texture, char *filename) { SDL_Surface *image = NULL; // 画像の読み込み image = IMG_Load(filename); if(!image) { printf("IMG_Load: %s\n", IMG_GetError()); return 1; } // 透過色の設定 SDL_SetColorKey(image, SDL_TRUE, SDL_MapRGB(image->format, 255, 0, 255)); *image_texture = SDL_CreateTextureFromSurface(renderer, image); SDL_FreeSurface(image); return 0; } int player_animation(SDL_Renderer *renderer) { SDL_Texture *cat_image = NULL; load_image(renderer, &cat_image, "image/charachip/chiharu.bmp"); // load_image(renderer, &cat_image, "image/charachip/white_cat.bmp"); int x = ((frame / animecycle) % 4) * 16; int y = player.direction * IMAGE_HEIGHT; SDL_Rect imageRect=(SDL_Rect){x, y, IMAGE_WIDTH, IMAGE_HEIGHT}; SDL_Rect drawRect=(SDL_Rect){player.pixel_x - player.offset_x, player.pixel_y - player.offset_y, IMAGE_WIDTH*MAGNIFICATION, IMAGE_HEIGHT*MAGNIFICATION}; SDL_RenderCopy(renderer, cat_image, &imageRect, &drawRect); if (frame <= animecycle * 4) { frame += 1; } else{ frame = 0; } SDL_DestroyTexture(cat_image); return 0; } int player_update(SDL_Renderer *renderer, SDL_Event e) { if (player.moving == TRUE) { player.pixel_x = player.pixel_x + player.velocity_x; player.pixel_y = player.pixel_y + player.velocity_y; if (player.pixel_x % GRID_SIZE == 0 && player.pixel_y % GRID_SIZE == 0){ player.moving = FALSE; player.map_x = player.pixel_x / GRID_SIZE; player.map_y = player.pixel_y / GRID_SIZE; load_move(renderer); player_move(e); } } else { player_move(e); } } int player_move(SDL_Event e) { if (message_window.visible == OUT_VISIBLE) { if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_UP){ player.direction = UP; if (is_movable(player.map_x, player.map_y - 1) == 0) { player.velocity_x = 0; player.velocity_y = -speed; player.moving = TRUE; } } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_DOWN){ player.direction = DOWN; if (is_movable(player.map_x, player.map_y + 1) == 0) { player.velocity_x = 0; player.velocity_y = speed; player.moving = TRUE; } } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_RIGHT){ player.direction = RIGHT; if (is_movable(player.map_x + 1, player.map_y) == 0) { player.velocity_x = speed; player.velocity_y = 0; player.moving = TRUE; } } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_LEFT){ player.direction = LEFT; if (is_movable(player.map_x - 1, player.map_y) == 0) { player.velocity_x = -speed; player.velocity_y = 0; player.moving = TRUE; } } } return 0; } int load_npc(SDL_Renderer *renderer) { char event_path[256] = {0}; sprintf(event_path, "data/%s.evt", MAP_EVENT_NAME); FILE *fp; char event[256] = {0}; char npc_name[256] = {0}; int map_x; int map_y; DIRECTION direction; MOVING moving; int max_step; int message_length; char message[1024] = {0}; char buf[256] = {0}; char npc_path[256] = {0}; int i = 0; int element_number = 0; fp = fopen(event_path, "r"); if (fp == NULL) { printf("file open error. %d\n", __LINE__); return 1; } for (i = 0;i < number_of_npc_image;i++) { npc[i].npc.map_x = 0; npc[i].npc.map_y = 0; npc[i].npc.pixel_x = 0; npc[i].npc.pixel_y = 0; npc[i].npc.direction = 0; npc[i].npc.moving = 0; SDL_DestroyTexture(npc[i].npc_image); sprintf(npc[i].message, "%s", '\0'); } for(i = 0;fgets(buf, sizeof(buf), fp) != NULL;i++) { if (strncmp(buf, "#", 1) != 0){ if (strncmp(buf, "CHARA", 5) == 0) { sscanf(buf, "%[^,],%[^,],%d,%d,%d,%d,%[^,]", event, npc_name, &map_x, &map_y, &direction, &moving, message); sprintf(npc_path, "image/charachip/%s.bmp", npc_name); load_image(renderer, &npc[element_number].npc_image, npc_path); npc[element_number].npc.map_x = map_x; npc[element_number].npc.map_y = map_y; npc[element_number].npc.pixel_x = map_x * GRID_SIZE; npc[element_number].npc.pixel_y = map_y * GRID_SIZE; npc[element_number].npc.direction = direction; npc[element_number].npc_move = moving; message_length = strlen(message); message[message_length - 1] = '\0'; sprintf(npc[element_number].message, "%s", message); element_number += 1; } } } number_of_npc_image = element_number; fclose(fp); return 0; } int npc_animation(SDL_Renderer *renderer) { int i; for(i = 0; number_of_npc_image >= i;i++) { int x = ((frame / animecycle) % 4) * 16; int y = npc[i].npc.direction * IMAGE_HEIGHT; SDL_Rect imageRect=(SDL_Rect){x/4, y/4, IMAGE_WIDTH/4, IMAGE_HEIGHT/4}; SDL_Rect drawRect=(SDL_Rect){npc[i].npc.pixel_x - player.offset_x, npc[i].npc.pixel_y - player.offset_y, IMAGE_WIDTH*MAGNIFICATION, IMAGE_HEIGHT*MAGNIFICATION}; SDL_RenderCopy(renderer, npc[i].npc_image, &imageRect, &drawRect); if (npc[i].npc_move == TRUE && number_of_npc_image != 0) { npc_update(renderer, i); } } return 0; } int npc_update(SDL_Renderer *renderer, int element) { srand((unsigned)time(NULL)); int target = rand()%number_of_npc_image; int action = rand()%10; if (npc[element].npc_move == TRUE) { if (npc[element].npc.moving == TRUE) { npc[element].npc.pixel_x = npc[element].npc.pixel_x + npc[element].npc.velocity_x; npc[element].npc.pixel_y = npc[element].npc.pixel_y + npc[element].npc.velocity_y; if (npc[element].npc.pixel_x % GRID_SIZE == 0 && npc[element].npc.pixel_y % GRID_SIZE == 0) { npc[element].npc.moving = FALSE; npc[element].npc.map_x = npc[element].npc.pixel_x / GRID_SIZE; npc[element].npc.map_y = npc[element].npc.pixel_y / GRID_SIZE; } } else { if (target == element && action < 3) { npc_move(rand()%4, element); } } } } int npc_move(DIRECTION direction, int element) { if (message_window.visible == OUT_VISIBLE) { if (frame == 0) { if (direction == UP){ npc[element].npc.direction = UP; if (is_movable(npc[element].npc.map_x, npc[element].npc.map_y - 1) == 0) { npc[element].npc.velocity_x = 0; npc[element].npc.velocity_y = -speed; npc[element].npc.moving = TRUE; } } else if (direction == DOWN){ npc[element].npc.direction = DOWN; if (is_movable(npc[element].npc.map_x, npc[element].npc.map_y + 1) == 0) { npc[element].npc.velocity_x = 0; npc[element].npc.velocity_y = speed; npc[element].npc.moving = TRUE; } } else if (direction == RIGHT){ npc[element].npc.direction = RIGHT; if (is_movable(npc[element].npc.map_x + 1, npc[element].npc.map_y) == 0) { npc[element].npc.velocity_x = speed; npc[element].npc.velocity_y = 0; npc[element].npc.moving = TRUE; } } else if (direction == LEFT){ npc[element].npc.direction = LEFT; if (is_movable(npc[element].npc.map_x - 1, npc[element].npc.map_y) == 0) { npc[element].npc.velocity_x = -speed; npc[element].npc.velocity_y = 0; npc[element].npc.moving = TRUE; } } } } return 0; } int draw_map(SDL_Renderer *renderer){ int x, y; int start_x = player.offset_x / GRID_SIZE - 1; int end_x = start_x + SCREEN_WIDTH / GRID_SIZE + 2; int start_y = player.offset_y / GRID_SIZE - 1; int end_y = start_y + SCREEN_HEIGHT/ GRID_SIZE + 2; for(y = start_y;y < end_y;y++){ for(x = start_x; x < end_x;x++){ SDL_Rect imageRect=(SDL_Rect){0, 0, IMAGE_WIDTH, IMAGE_HEIGHT}; SDL_Rect drawRect=(SDL_Rect){(x * GRID_SIZE) - player.offset_x, (y * GRID_SIZE) - player.offset_y, IMAGE_WIDTH*MAGNIFICATION, IMAGE_HEIGHT*MAGNIFICATION}; if ((x < 0) || (x > COL - 1) || (y < 0) || (y > ROW - 1)){ SDL_RenderCopy(renderer, mapchip[OUT_OF_MAP].map_image, &imageRect, &drawRect); } else { SDL_RenderCopy(renderer, mapchip[map_array[y*COL+x]].map_image, &imageRect, &drawRect); } } } return 0; } int load_move(SDL_Renderer *renderer) { char event_path[256] = {0}; sprintf(event_path, "data/%s.evt", MAP_EVENT_NAME); FILE *fp; char event[256] = {0}; int event_point_x; int event_point_y; DIRECTION direction_of_penetration; char buf[256] = {0}; char new_map_name[256] = {0}; char map_path[256] = {0}; int new_x; int new_y; int i = 0; fp = fopen(event_path, "r"); if (fp == NULL) { printf("file open error. %d\n", __LINE__); return 1; } for(i = 0;fgets(buf, sizeof(buf), fp) != NULL;i++) { if (strncmp(buf, "#", 1) != 0){ if (strncmp(buf, "MOVE", 4) == 0) { sscanf(buf, "%[^,],%d,%d,%d,%[^,],%d,%d", event, &event_point_x, &event_point_y, &direction_of_penetration, new_map_name, &new_x, &new_y); if (player.map_x == event_point_x && player.map_y == event_point_y) { if (player.direction == direction_of_penetration) { sprintf(MAP_EVENT_NAME, "%s", new_map_name); sprintf(map_path, "data/%s.map", new_map_name); load_map(map_path); player.map_x = new_x; player.map_y = new_y; player.pixel_x = player.map_x * GRID_SIZE; player.pixel_y = player.map_y * GRID_SIZE; load_npc(renderer); fade_out(renderer); break; } } } } } fclose(fp); return 0; } int fade_out(SDL_Renderer *renderer) { SDL_Rect rectangle; SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); rectangle.x = 0; rectangle.y = 0; rectangle.w = SCREEN_WIDTH; rectangle.h = SCREEN_HEIGHT; int i = 200; int inverse_flg = 0; while(1) { SDL_SetRenderDrawColor(renderer, 0, 0, 0, i); SDL_RenderFillRect(renderer, &rectangle); SDL_RenderPresent(renderer); if (inverse_flg == 0 && i <= 255) { i = i + 5; SDL_Delay(80); } if (i == 255) { inverse_flg = 1; } if (inverse_flg == 1) { clac_offset(player.pixel_x, player.pixel_y, &player.offset_x, &player.offset_y); draw_map(renderer); player_animation(renderer); SDL_Delay(20); i = i - 5; if (i == 200) { break; } } } return 0; } int load_map(char *map_name) { FILE *fp; int i = 0; if ((fp = fopen(map_name, "rb")) == NULL) { return 1; } fread(&COL, sizeof(int), 1, fp); fread(&ROW, sizeof(int), 1, fp); fread(&OUT_OF_MAP, sizeof(int), 1, fp); map_array = realloc(map_array, sizeof(int) * COL * ROW); while(!feof(fp)) { fread(&map_array[i++], sizeof(int), 1, fp); } fclose(fp); return 0; } int load_mapchip(SDL_Renderer *renderer) { FILE *fp; int x, y, z; char n[256] = {0}; char path[256] = {0}; char buf[256] = {0}; int i = 0; fp = fopen("data/mapchip.dat", "r"); if (fp == NULL) { printf("file open error. %d\n", __LINE__); return 1; } for(i = 0;fgets(buf, sizeof(buf), fp) != NULL;i++){ sscanf(buf, "%d,%[^,],%d,%d", &x, n, &y, &z); mapchip[i].mapchip_id = x; strcpy(mapchip[i].mapchip_name, n); mapchip[i].movable = y; mapchip[i].change_locate = z; sprintf(path, "image/mapchip/%s.bmp", mapchip[i].mapchip_name); load_image(renderer, &mapchip[i].map_image, path); } number_of_map_image = i - 1; fclose(fp); return 0; } int is_movable(int x, int y) { int i; for(i = 0;i < number_of_npc_image;i++) { if (npc[i].npc.map_x == x && npc[i].npc.map_y == y) { return 1; } } if ( x < 0 || x > COL - 1 || y < 0 || y > ROW - 1) { return 2; } if (mapchip[map_array[y*COL+x]].movable == 1) { return 2; } if(player.map_x == x && player.map_y == y) { return 1; } return 0; } int clac_offset(int x, int y, int *offset_x, int *offset_y) { *offset_x = x - (SCREEN_WIDTH / 2); *offset_y = y - (SCREEN_HEIGHT / 2); return 0; } int make_box(SDL_Renderer *renderer, int x, int y, int w, int h, int blend, int r, int g, int b) { SDL_Rect rectangle; SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); rectangle.x = x; rectangle.y = y; rectangle.w = w; rectangle.h = h; SDL_SetRenderDrawColor(renderer, r, g, b, blend); SDL_RenderFillRect(renderer, &rectangle); SDL_RenderPresent(renderer); return 0; } int make_triangle(SDL_Renderer *renderer, int x1, int y1, int x2, int y2, int x3, int y3, int blend, int r, int g, int b) { SDL_Point points[4] = {{x1, y1},{x2, y2}, {x3, y3}, {x1, y1}}; SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); SDL_SetRenderDrawColor(renderer, r, g, b, blend); SDL_RenderDrawLines(renderer, points, 4); SDL_RenderPresent(renderer); return 0; } int make_window(SDL_Renderer *renderer, WINDOW window) { int edge_size = 4; SDL_Rect rectangle; SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); rectangle.x = window.rectangle_x; rectangle.y = window.rectangle_y; rectangle.w = window.rectangle_w; rectangle.h = window.rectangle_h; // WHITE SDL_SetRenderDrawColor(renderer, 255, 255, 255, window.blend); SDL_RenderFillRect(renderer, &rectangle); rectangle.x = window.rectangle_x + edge_size; rectangle.y = window.rectangle_y + edge_size; rectangle.w = window.rectangle_w - edge_size * 2; rectangle.h = window.rectangle_h - edge_size * 2; // BLACK SDL_SetRenderDrawColor(renderer, 0, 0, 0, window.blend); SDL_RenderFillRect(renderer, &rectangle); return 0; } int window_engine(SDL_Renderer *renderer, WINDOW window) { make_window(renderer, window); return 0; } int window_update(SDL_Renderer *renderer, TTF_Font *font, SDL_Event e) { if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_SPACE){ if (message_window.visible == OUT_VISIBLE) { message_window.visible = IN_VISIBLE; } else { message_window.visible = OUT_VISIBLE; } } if (message_window.visible == IN_VISIBLE) { window_engine(renderer, message_window); message_engine(renderer, font, e); } return 0; } int message_engine(SDL_Renderer *renderer, TTF_Font *font, SDL_Event e) { int x; int message_length; int remaining_message_length; int word_length = 0; int byte_counter; int byt; char message_tmp[1024]; char word[6]; if (state == OFF) { get_character_message(e, &message); state = ON; } else { strcpy(message_tmp, message); message_length = strlen(message); while (*message != '\0') { byt = u8mb(*message); message += u8mb(*message); remaining_message_length = strlen(message); memset(word, '\0', 6); byte_counter = 0; for (x=word_length;x<message_length - remaining_message_length;x++) { sprintf(&word[byte_counter], "%c", message_tmp[x]); byte_counter++; } SDL_Delay(120); display_character_string(renderer, font, word, 155 + word_length * 5 , 354); word_length = message_length - remaining_message_length; if ( SDL_PollEvent(&e) ) { if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_SPACE){ display_character_string(renderer, font, message, 155 + word_length * 5 , 354); break; } } } if (message_window.visible == IN_VISIBLE) { while (1) { SDL_Delay(400); if (state == ON) { make_triangle(renderer, 310, 450, 320, 450, 315, 460, 255, 255, 255, 255); state = OFF; } else { make_box(renderer, 310, 442, 14, 22, 255, 0, 0, 0); state = ON; } if ( SDL_PollEvent(&e) ) { if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_SPACE){ message_window.visible = OUT_VISIBLE; break; } } } } state = OFF; } return 0; } int get_character_message(SDL_Event e, char **message) { int i; *message = "そっちには だれも いないよ!"; if (player.direction == UP) { if (is_movable(player.map_x, player.map_y - 1) == 1) { for(i = 0;i < number_of_npc_image;i++) { if (npc[i].npc.map_x == player.map_x && npc[i].npc.map_y == player.map_y - 1) { npc[i].npc.direction = DOWN; *message = npc[i].message; break; } } } } else if (player.direction == DOWN) { if (is_movable(player.map_x, player.map_y + 1) == 1) { for(i = 0;i < number_of_npc_image;i++) { if (npc[i].npc.map_x == player.map_x && npc[i].npc.map_y == player.map_y + 1) { npc[i].npc.direction = UP; *message = npc[i].message; break; } } } } else if (player.direction == RIGHT) { if (is_movable(player.map_x + 1, player.map_y) == 1) { for(i = 0;i < number_of_npc_image;i++) { if (npc[i].npc.map_x == player.map_x + 1 && npc[i].npc.map_y == player.map_y) { npc[i].npc.direction = LEFT; *message = npc[i].message; break; } } } } else if (player.direction == LEFT) { if (is_movable(player.map_x - 1, player.map_y) == 1) { for(i = 0;i < number_of_npc_image;i++) { if (npc[i].npc.map_x == player.map_x - 1 && npc[i].npc.map_y == player.map_y) { npc[i].npc.direction = RIGHT; *message = npc[i].message; break; } } } } return 0; } int display_character_string(SDL_Renderer *renderer, TTF_Font *font, char *string, int x, int y) { SDL_Surface *surface; SDL_Texture *texture; surface = TTF_RenderUTF8_Blended(font, string, (SDL_Color){255,255,255,255}); texture = SDL_CreateTextureFromSurface(renderer, surface); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); int iw,ih; SDL_QueryTexture(texture, NULL, NULL, &iw, &ih); SDL_Rect txtRect=(SDL_Rect){0,0,iw,ih}; SDL_Rect pasteRect=(SDL_Rect){x,y,iw,ih}; SDL_RenderCopy(renderer, texture, &txtRect, &pasteRect); SDL_RenderPresent(renderer); SDL_FreeSurface(surface); return 0; } // UTF-8文字の1バイト目を判定して文字のバイト数を返す関数 int u8mb(const char chr) { int byt = 1; if ((chr & 0x80) == 0x00) { //1byte文字は何もしない([byt = 1]のまま) } else if ((chr & 0xE0) == 0xC0) { //2byte文字 byt = 2; } else if ((chr & 0xF0) == 0xE0) { //3byte文字 byt = 3; } else if ((chr & 0xF8) == 0xF0) { //4byte文字 byt = 4; } else if ((chr & 0xFC) == 0xF8) { //5byte文字 byt = 5; } else if ((chr & 0xFE) == 0xFC) { //6byte文字 byt = 6; } return byt; }
実行結果
実行結果はこんな感じ。三角形の点滅ができた。
終わりに
今回は三角形の点滅を表示した。次回は折り返しや改行、改ページの辺りを実装しようと思う。
SDL2でRPGゲームを作る 〜第13回 メッセージボックスの作成 その2〜
前回は、NPCに話しかけたら、メッセージボックスが表示されて、その中に文字が表示されるといったところまで作ったので今回は、流れるメッセージを作っていきたいと思う。では、早速作っていこう。
流れるメッセージを実現するためには
流れるメッセージを実現するためには以下の2つが実装できれば良いはず。
1. 読み込んだ文字列を1文字ずつ取得する
2. 1文字ずつ、ずらしながら表示する
順番に実装していこうと思う。
読み込んだ文字列を1文字ずつ取得する
まず、文字を1文字ずつ取得する必要がある。文字を1文字ずつ取得するためには読み込んでいる文字の文字コードが何かを意識する必要がある。
例えば、SJISであれば2バイト区切りでいいし、UTF8ならば文字によってに使用しているバイト数が違うので判定処理を作る必要がある。現在使用しているイベントファイルの文字コードはUTF8を使ってるので判定処理が必要になる。
判定の仕方だが、UTF8の文字コードは文字の1バイト目のバイトコードを判定することでその文字のバイト数が決まる。よって、以下の実装で文字のバイト数を判定する。
int u8mb(const char chr) { int byt = 1; if ((chr & 0x80) == 0x00) { //1byte文字は何もしない([byt = 1]のまま) } else if ((chr & 0xE0) == 0xC0) { //2byte文字 byt = 2; } else if ((chr & 0xF0) == 0xE0) { //3byte文字 byt = 3; } else if ((chr & 0xF8) == 0xF0) { //4byte文字 byt = 4; } else if ((chr & 0xFC) == 0xF8) { //5byte文字 byt = 5; } else if ((chr & 0xFE) == 0xFC) { //6byte文字 byt = 6; } return byt; }
この関数は、文字列の先頭1バイトを受け取ってその文字が何バイト文字かを判定する。よって、リターン値分のバイト数を文字列から切り取って行くことで文字を1文字ずつ取得することができる。
1文字ずつ、ずらしながら表示する
上記の関数を使用すると文字列から1文字を切り取って取得することができるので、今度はそれを順に表示する必要がある。流れるメッセージにするためには、文字を表示後1文字分ずらして行くことが大事になる。
それを、意識しながら作成した関数が以下になる。
int message_engine(SDL_Renderer *renderer, TTF_Font *font, SDL_Event e) { int x; int message_length; int remaining_message_length; int word_length = 0; int byte_counter; int byt; char message_tmp[1024]; char word[6]; if (state == OFF) { get_character_message(e, &message); state = ON; } else { strcpy(message_tmp, message); message_length = strlen(message); while (*message != '\0') { byt = u8mb(*message); message += u8mb(*message); remaining_message_length = strlen(message); memset(word, '\0', 6); byte_counter = 0; for (x=word_length;x<message_length - remaining_message_length;x++) { sprintf(&word[byte_counter], "%c", message_tmp[x]); byte_counter++; } SDL_Delay(120); display_character_string(renderer, font, word, 155 + word_length * 6 , 354); word_length = message_length - remaining_message_length; if ( SDL_PollEvent(&e) ) { if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_SPACE){ message_window.visible = OUT_VISIBLE; break; } } } if (message_window.visible == IN_VISIBLE) { while (1) { if ( SDL_PollEvent(&e) ) { if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_SPACE){ message_window.visible = OUT_VISIBLE; break; } } } } state = OFF; } return 0; }
色々と処理が書いてあるが流れるメッセージを表示するために大事な部分はwhile文の部分だ。
u8md関数
で1文字のバイト数を取得し、必要なバイト数文for文で文字列からバイトコードを取得する。その後、display_character_string関数
で文字を1文字ずつずらして表示している。
全コード表示
では、全コードを記載する。
window.h
#ifdef _RPGINC #else #include <stdio.h> #include <SDL2/SDL.h> #include <SDL2/SDL_image.h> #include <SDL2/SDL_ttf.h> typedef enum {DOWN, LEFT, RIGHT, UP} DIRECTION; typedef enum {FALSE, TRUE} MOVING; typedef enum {OUT_VISIBLE, IN_VISIBLE} VISIBLE; typedef enum {OFF, ON} STATE; typedef struct { int map_x; int map_y; int pixel_x; int pixel_y; int offset_x; int offset_y; int velocity_x; int velocity_y; DIRECTION direction; MOVING moving; } CARACTER; typedef struct { CARACTER npc; char message[1024]; SDL_Texture *npc_image; MOVING npc_move; } NPC; typedef struct { int mapchip_id; char mapchip_name[256]; int movable; int change_locate; SDL_Texture *map_image; } MAPCHIP; typedef struct { int rectangle_x; int rectangle_y; int rectangle_w; int rectangle_h; int blend; VISIBLE visible; } WINDOW; int clac_offset(int, int, int *, int *); int load_image(SDL_Renderer *, SDL_Texture **, char *); int player_animation(SDL_Renderer *); int player_update(SDL_Renderer *, SDL_Event); int player_move(SDL_Event); int load_npc(SDL_Renderer *); int npc_animation(SDL_Renderer *); int npc_update(SDL_Renderer *renderer, int); int npc_move(DIRECTION, int); int load_map_image(SDL_Renderer *, SDL_Texture **); int load_mapchip(SDL_Renderer *); int load_move(SDL_Renderer *); int load_map(char *); int draw_map(SDL_Renderer *); int is_movable(int, int); int fade_out(SDL_Renderer *); int make_window(SDL_Renderer *, WINDOW); int make_box(SDL_Renderer *, int, int, int, int, int, int, int, int); int make_triangle(SDL_Renderer *, int, int, int, int, int, int, int, int, int, int); int window_update(SDL_Renderer *, TTF_Font *, SDL_Event); int window_engine(SDL_Renderer *, WINDOW); int message_engine(SDL_Renderer *, TTF_Font *, SDL_Event); int display_character_string(SDL_Renderer *, TTF_Font *, char *, int, int); int get_character_message(SDL_Event, char **); int u8mb(const char); #endif
window.c
#include <stdio.h> #include <stdlib.h> #include <time.h> #include <SDL2/SDL.h> #include <SDL2/SDL_image.h> #include <SDL2/SDL_ttf.h> #include "RPGInC.h" #define FONT_PATH "font/PixelMplus12-Regular.ttf" const int SCREEN_WIDTH = 640; const int SCREEN_HEIGHT = 480; const int IMAGE_WIDTH = 16; const int IMAGE_HEIGHT = 16; const int MAGNIFICATION = 2; const int GRID_SIZE = 32; const int FONT_SIZE = 16; int ROW = 15; int COL = 20; int OUT_OF_MAP = 0; char MAP_EVENT_NAME[256] = "field"; int animecycle = 24; int speed = 2; int frame = 0; int number_of_map_image = 0; int number_of_npc_image = 0; CARACTER player = {1, 1, 32, 32, 0, 0, 0, 0, DOWN, FALSE}; NPC npc[256] = {0}; MAPCHIP mapchip[256] = {0}; int *map_array; WINDOW message_window = {140, 334, 360, 140, 255, OUT_VISIBLE}; char *message; STATE state = OFF; int main (int argc, char *argv[]) { SDL_Window *window = NULL; SDL_Renderer *renderer = NULL; TTF_Font *font = NULL; //Initialize SDL if( SDL_Init( SDL_INIT_VIDEO ) < 0 ) { printf( "SDL could not initialize! SDL_Error: %s\n", SDL_GetError() ); return 1; } window = SDL_CreateWindow( "DRAW IMAGE TEST", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN ); if( window == NULL ) { printf( "Window could not be created! SDL_Error: %s\n", SDL_GetError() ); return 1; } else { renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); } //Initialize TTF if ( TTF_Init() < 0 ) { printf("TTFcould not initialize! TTF_Error: %s\n", TTF_GetError()); } font = TTF_OpenFont(FONT_PATH, FONT_SIZE); if ( font == NULL ) { printf("TTF_OpenFont: %s\n", TTF_GetError()); } load_mapchip(renderer); load_map("data/field.map"); load_npc(renderer); // main loop while (1) { SDL_Delay(5); SDL_Event e; clac_offset(player.pixel_x, player.pixel_y, &player.offset_x, &player.offset_y); SDL_RenderClear(renderer); draw_map(renderer); npc_animation(renderer); player_animation(renderer); player_update(renderer, e); window_update(renderer, font, e); SDL_RenderPresent(renderer); // event handling if ( SDL_PollEvent(&e) ) { if (e.type == SDL_QUIT){ break; } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE){ break; } } } IMG_Quit(); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); int i; for (i = 0;i < number_of_map_image;i++) { SDL_DestroyTexture(mapchip[i].map_image); } for (i = 0;i < number_of_npc_image;i++) { SDL_DestroyTexture(npc[i].npc_image); } SDL_Quit(); return 0; } int load_image(SDL_Renderer *renderer, SDL_Texture **image_texture, char *filename) { SDL_Surface *image = NULL; // 画像の読み込み image = IMG_Load(filename); if(!image) { printf("IMG_Load: %s\n", IMG_GetError()); return 1; } // 透過色の設定 SDL_SetColorKey(image, SDL_TRUE, SDL_MapRGB(image->format, 255, 0, 255)); *image_texture = SDL_CreateTextureFromSurface(renderer, image); SDL_FreeSurface(image); return 0; } int player_animation(SDL_Renderer *renderer) { SDL_Texture *cat_image = NULL; load_image(renderer, &cat_image, "image/charachip/chiharu.bmp"); // load_image(renderer, &cat_image, "image/charachip/white_cat.bmp"); int x = ((frame / animecycle) % 4) * 16; int y = player.direction * IMAGE_HEIGHT; SDL_Rect imageRect=(SDL_Rect){x, y, IMAGE_WIDTH, IMAGE_HEIGHT}; SDL_Rect drawRect=(SDL_Rect){player.pixel_x - player.offset_x, player.pixel_y - player.offset_y, IMAGE_WIDTH*MAGNIFICATION, IMAGE_HEIGHT*MAGNIFICATION}; SDL_RenderCopy(renderer, cat_image, &imageRect, &drawRect); if (frame <= animecycle * 4) { frame += 1; } else{ frame = 0; } SDL_DestroyTexture(cat_image); return 0; } int player_update(SDL_Renderer *renderer, SDL_Event e) { if (player.moving == TRUE) { player.pixel_x = player.pixel_x + player.velocity_x; player.pixel_y = player.pixel_y + player.velocity_y; if (player.pixel_x % GRID_SIZE == 0 && player.pixel_y % GRID_SIZE == 0){ player.moving = FALSE; player.map_x = player.pixel_x / GRID_SIZE; player.map_y = player.pixel_y / GRID_SIZE; load_move(renderer); player_move(e); } } else { player_move(e); } } int player_move(SDL_Event e) { if (message_window.visible == OUT_VISIBLE) { if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_UP){ player.direction = UP; if (is_movable(player.map_x, player.map_y - 1) == 0) { player.velocity_x = 0; player.velocity_y = -speed; player.moving = TRUE; } } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_DOWN){ player.direction = DOWN; if (is_movable(player.map_x, player.map_y + 1) == 0) { player.velocity_x = 0; player.velocity_y = speed; player.moving = TRUE; } } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_RIGHT){ player.direction = RIGHT; if (is_movable(player.map_x + 1, player.map_y) == 0) { player.velocity_x = speed; player.velocity_y = 0; player.moving = TRUE; } } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_LEFT){ player.direction = LEFT; if (is_movable(player.map_x - 1, player.map_y) == 0) { player.velocity_x = -speed; player.velocity_y = 0; player.moving = TRUE; } } } return 0; } int load_npc(SDL_Renderer *renderer) { char event_path[256] = {0}; sprintf(event_path, "data/%s.evt", MAP_EVENT_NAME); FILE *fp; char event[256] = {0}; char npc_name[256] = {0}; int map_x; int map_y; DIRECTION direction; MOVING moving; int max_step; int message_length; char message[1024] = {0}; char buf[256] = {0}; char npc_path[256] = {0}; int i = 0; int element_number = 0; fp = fopen(event_path, "r"); if (fp == NULL) { printf("file open error. %d\n", __LINE__); return 1; } for (i = 0;i < number_of_npc_image;i++) { npc[i].npc.map_x = 0; npc[i].npc.map_y = 0; npc[i].npc.pixel_x = 0; npc[i].npc.pixel_y = 0; npc[i].npc.direction = 0; npc[i].npc.moving = 0; SDL_DestroyTexture(npc[i].npc_image); sprintf(npc[i].message, "%s", '\0'); } for(i = 0;fgets(buf, sizeof(buf), fp) != NULL;i++) { if (strncmp(buf, "#", 1) != 0){ if (strncmp(buf, "CHARA", 5) == 0) { sscanf(buf, "%[^,],%[^,],%d,%d,%d,%d,%[^,]", event, npc_name, &map_x, &map_y, &direction, &moving, message); sprintf(npc_path, "image/charachip/%s.bmp", npc_name); load_image(renderer, &npc[element_number].npc_image, npc_path); npc[element_number].npc.map_x = map_x; npc[element_number].npc.map_y = map_y; npc[element_number].npc.pixel_x = map_x * GRID_SIZE; npc[element_number].npc.pixel_y = map_y * GRID_SIZE; npc[element_number].npc.direction = direction; npc[element_number].npc_move = moving; message_length = strlen(message); message[message_length - 1] = '\0'; sprintf(npc[element_number].message, "%s", message); element_number += 1; } } } number_of_npc_image = element_number; fclose(fp); return 0; } int npc_animation(SDL_Renderer *renderer) { int i; for(i = 0; number_of_npc_image >= i;i++) { int x = ((frame / animecycle) % 4) * 16; int y = npc[i].npc.direction * IMAGE_HEIGHT; SDL_Rect imageRect=(SDL_Rect){x/4, y/4, IMAGE_WIDTH/4, IMAGE_HEIGHT/4}; SDL_Rect drawRect=(SDL_Rect){npc[i].npc.pixel_x - player.offset_x, npc[i].npc.pixel_y - player.offset_y, IMAGE_WIDTH*MAGNIFICATION, IMAGE_HEIGHT*MAGNIFICATION}; SDL_RenderCopy(renderer, npc[i].npc_image, &imageRect, &drawRect); if (npc[i].npc_move == TRUE && number_of_npc_image != 0) { npc_update(renderer, i); } } return 0; } int npc_update(SDL_Renderer *renderer, int element) { srand((unsigned)time(NULL)); int target = rand()%number_of_npc_image; int action = rand()%10; if (npc[element].npc_move == TRUE) { if (npc[element].npc.moving == TRUE) { npc[element].npc.pixel_x = npc[element].npc.pixel_x + npc[element].npc.velocity_x; npc[element].npc.pixel_y = npc[element].npc.pixel_y + npc[element].npc.velocity_y; if (npc[element].npc.pixel_x % GRID_SIZE == 0 && npc[element].npc.pixel_y % GRID_SIZE == 0) { npc[element].npc.moving = FALSE; npc[element].npc.map_x = npc[element].npc.pixel_x / GRID_SIZE; npc[element].npc.map_y = npc[element].npc.pixel_y / GRID_SIZE; } } else { if (target == element && action < 3) { npc_move(rand()%4, element); } } } } int npc_move(DIRECTION direction, int element) { if (message_window.visible == OUT_VISIBLE) { if (frame == 0) { if (direction == UP){ npc[element].npc.direction = UP; if (is_movable(npc[element].npc.map_x, npc[element].npc.map_y - 1) == 0) { npc[element].npc.velocity_x = 0; npc[element].npc.velocity_y = -speed; npc[element].npc.moving = TRUE; } } else if (direction == DOWN){ npc[element].npc.direction = DOWN; if (is_movable(npc[element].npc.map_x, npc[element].npc.map_y + 1) == 0) { npc[element].npc.velocity_x = 0; npc[element].npc.velocity_y = speed; npc[element].npc.moving = TRUE; } } else if (direction == RIGHT){ npc[element].npc.direction = RIGHT; if (is_movable(npc[element].npc.map_x + 1, npc[element].npc.map_y) == 0) { npc[element].npc.velocity_x = speed; npc[element].npc.velocity_y = 0; npc[element].npc.moving = TRUE; } } else if (direction == LEFT){ npc[element].npc.direction = LEFT; if (is_movable(npc[element].npc.map_x - 1, npc[element].npc.map_y) == 0) { npc[element].npc.velocity_x = -speed; npc[element].npc.velocity_y = 0; npc[element].npc.moving = TRUE; } } } } return 0; } int draw_map(SDL_Renderer *renderer){ int x, y; int start_x = player.offset_x / GRID_SIZE - 1; int end_x = start_x + SCREEN_WIDTH / GRID_SIZE + 2; int start_y = player.offset_y / GRID_SIZE - 1; int end_y = start_y + SCREEN_HEIGHT/ GRID_SIZE + 2; for(y = start_y;y < end_y;y++){ for(x = start_x; x < end_x;x++){ SDL_Rect imageRect=(SDL_Rect){0, 0, IMAGE_WIDTH, IMAGE_HEIGHT}; SDL_Rect drawRect=(SDL_Rect){(x * GRID_SIZE) - player.offset_x, (y * GRID_SIZE) - player.offset_y, IMAGE_WIDTH*MAGNIFICATION, IMAGE_HEIGHT*MAGNIFICATION}; if ((x < 0) || (x > COL - 1) || (y < 0) || (y > ROW - 1)){ SDL_RenderCopy(renderer, mapchip[OUT_OF_MAP].map_image, &imageRect, &drawRect); } else { SDL_RenderCopy(renderer, mapchip[map_array[y*COL+x]].map_image, &imageRect, &drawRect); } } } return 0; } int load_move(SDL_Renderer *renderer) { char event_path[256] = {0}; sprintf(event_path, "data/%s.evt", MAP_EVENT_NAME); FILE *fp; char event[256] = {0}; int event_point_x; int event_point_y; DIRECTION direction_of_penetration; char buf[256] = {0}; char new_map_name[256] = {0}; char map_path[256] = {0}; int new_x; int new_y; int i = 0; fp = fopen(event_path, "r"); if (fp == NULL) { printf("file open error. %d\n", __LINE__); return 1; } for(i = 0;fgets(buf, sizeof(buf), fp) != NULL;i++) { if (strncmp(buf, "#", 1) != 0){ if (strncmp(buf, "MOVE", 4) == 0) { sscanf(buf, "%[^,],%d,%d,%d,%[^,],%d,%d", event, &event_point_x, &event_point_y, &direction_of_penetration, new_map_name, &new_x, &new_y); if (player.map_x == event_point_x && player.map_y == event_point_y) { if (player.direction == direction_of_penetration) { sprintf(MAP_EVENT_NAME, "%s", new_map_name); sprintf(map_path, "data/%s.map", new_map_name); load_map(map_path); player.map_x = new_x; player.map_y = new_y; player.pixel_x = player.map_x * GRID_SIZE; player.pixel_y = player.map_y * GRID_SIZE; load_npc(renderer); fade_out(renderer); break; } } } } } fclose(fp); return 0; } int fade_out(SDL_Renderer *renderer) { SDL_Rect rectangle; SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); rectangle.x = 0; rectangle.y = 0; rectangle.w = SCREEN_WIDTH; rectangle.h = SCREEN_HEIGHT; int i = 200; int inverse_flg = 0; while(1) { SDL_SetRenderDrawColor(renderer, 0, 0, 0, i); SDL_RenderFillRect(renderer, &rectangle); SDL_RenderPresent(renderer); if (inverse_flg == 0 && i <= 255) { i = i + 5; SDL_Delay(80); } if (i == 255) { inverse_flg = 1; } if (inverse_flg == 1) { clac_offset(player.pixel_x, player.pixel_y, &player.offset_x, &player.offset_y); draw_map(renderer); player_animation(renderer); SDL_Delay(20); i = i - 5; if (i == 200) { break; } } } return 0; } int load_map(char *map_name) { FILE *fp; int i = 0; if ((fp = fopen(map_name, "rb")) == NULL) { return 1; } fread(&COL, sizeof(int), 1, fp); fread(&ROW, sizeof(int), 1, fp); fread(&OUT_OF_MAP, sizeof(int), 1, fp); map_array = realloc(map_array, sizeof(int) * COL * ROW); while(!feof(fp)) { fread(&map_array[i++], sizeof(int), 1, fp); } fclose(fp); return 0; } int load_mapchip(SDL_Renderer *renderer) { FILE *fp; int x, y, z; char n[256] = {0}; char path[256] = {0}; char buf[256] = {0}; int i = 0; fp = fopen("data/mapchip.dat", "r"); if (fp == NULL) { printf("file open error. %d\n", __LINE__); return 1; } for(i = 0;fgets(buf, sizeof(buf), fp) != NULL;i++){ sscanf(buf, "%d,%[^,],%d,%d", &x, n, &y, &z); mapchip[i].mapchip_id = x; strcpy(mapchip[i].mapchip_name, n); mapchip[i].movable = y; mapchip[i].change_locate = z; sprintf(path, "image/mapchip/%s.bmp", mapchip[i].mapchip_name); load_image(renderer, &mapchip[i].map_image, path); } number_of_map_image = i - 1; fclose(fp); return 0; } int is_movable(int x, int y) { int i; for(i = 0;i < number_of_npc_image;i++) { if (npc[i].npc.map_x == x && npc[i].npc.map_y == y) { return 1; } } if ( x < 0 || x > COL - 1 || y < 0 || y > ROW - 1) { return 2; } if (mapchip[map_array[y*COL+x]].movable == 1) { return 2; } if(player.map_x == x && player.map_y == y) { return 1; } return 0; } int clac_offset(int x, int y, int *offset_x, int *offset_y) { *offset_x = x - (SCREEN_WIDTH / 2); *offset_y = y - (SCREEN_HEIGHT / 2); return 0; } int make_window(SDL_Renderer *renderer, WINDOW window) { int edge_size = 4; SDL_Rect rectangle; SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); rectangle.x = window.rectangle_x; rectangle.y = window.rectangle_y; rectangle.w = window.rectangle_w; rectangle.h = window.rectangle_h; // WHITE SDL_SetRenderDrawColor(renderer, 255, 255, 255, window.blend); SDL_RenderFillRect(renderer, &rectangle); rectangle.x = window.rectangle_x + edge_size; rectangle.y = window.rectangle_y + edge_size; rectangle.w = window.rectangle_w - edge_size * 2; rectangle.h = window.rectangle_h - edge_size * 2; // BLACK SDL_SetRenderDrawColor(renderer, 0, 0, 0, window.blend); SDL_RenderFillRect(renderer, &rectangle); return 0; } int window_engine(SDL_Renderer *renderer, WINDOW window) { make_window(renderer, window); return 0; } int window_update(SDL_Renderer *renderer, TTF_Font *font, SDL_Event e) { if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_SPACE){ if (message_window.visible == OUT_VISIBLE) { message_window.visible = IN_VISIBLE; } else { message_window.visible = OUT_VISIBLE; } } if (message_window.visible == IN_VISIBLE) { window_engine(renderer, message_window); message_engine(renderer, font, e); } return 0; } int message_engine(SDL_Renderer *renderer, TTF_Font *font, SDL_Event e) { int x; int message_length; int remaining_message_length; int word_length = 0; int byte_counter; int byt; char message_tmp[1024]; char word[6]; if (state == OFF) { get_character_message(e, &message); state = ON; } else { strcpy(message_tmp, message); message_length = strlen(message); while (*message != '\0') { byt = u8mb(*message); message += u8mb(*message); remaining_message_length = strlen(message); memset(word, '\0', 6); byte_counter = 0; for (x=word_length;x<message_length - remaining_message_length;x++) { sprintf(&word[byte_counter], "%c", message_tmp[x]); byte_counter++; } SDL_Delay(120); display_character_string(renderer, font, word, 155 + word_length * 6 , 354); word_length = message_length - remaining_message_length; if ( SDL_PollEvent(&e) ) { if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_SPACE){ message_window.visible = OUT_VISIBLE; break; } } } if (message_window.visible == IN_VISIBLE) { while (1) { if ( SDL_PollEvent(&e) ) { if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_SPACE){ message_window.visible = OUT_VISIBLE; break; } } } } state = OFF; } return 0; } int get_character_message(SDL_Event e, char **message) { int i; *message = "そっちには だれも いないよ!"; if (player.direction == UP) { if (is_movable(player.map_x, player.map_y - 1) == 1) { for(i = 0;i < number_of_npc_image;i++) { if (npc[i].npc.map_x == player.map_x && npc[i].npc.map_y == player.map_y - 1) { npc[i].npc.direction = DOWN; *message = npc[i].message; break; } } } } else if (player.direction == DOWN) { if (is_movable(player.map_x, player.map_y + 1) == 1) { for(i = 0;i < number_of_npc_image;i++) { if (npc[i].npc.map_x == player.map_x && npc[i].npc.map_y == player.map_y + 1) { npc[i].npc.direction = UP; *message = npc[i].message; break; } } } } else if (player.direction == RIGHT) { if (is_movable(player.map_x + 1, player.map_y) == 1) { for(i = 0;i < number_of_npc_image;i++) { if (npc[i].npc.map_x == player.map_x + 1 && npc[i].npc.map_y == player.map_y) { npc[i].npc.direction = LEFT; *message = npc[i].message; break; } } } } else if (player.direction == LEFT) { if (is_movable(player.map_x - 1, player.map_y) == 1) { for(i = 0;i < number_of_npc_image;i++) { if (npc[i].npc.map_x == player.map_x - 1 && npc[i].npc.map_y == player.map_y) { npc[i].npc.direction = RIGHT; *message = npc[i].message; break; } } } } return 0; } int display_character_string(SDL_Renderer *renderer, TTF_Font *font, char *string, int x, int y) { SDL_Surface *surface; SDL_Texture *texture; surface = TTF_RenderUTF8_Blended(font, string, (SDL_Color){255,255,255,255}); texture = SDL_CreateTextureFromSurface(renderer, surface); SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); int iw,ih; SDL_QueryTexture(texture, NULL, NULL, &iw, &ih); SDL_Rect txtRect=(SDL_Rect){0,0,iw,ih}; SDL_Rect pasteRect=(SDL_Rect){x,y,iw,ih}; SDL_RenderCopy(renderer, texture, &txtRect, &pasteRect); SDL_RenderPresent(renderer); SDL_FreeSurface(surface); return 0; } // UTF-8文字の1バイト目を判定して文字のバイト数を返す関数 int u8mb(const char chr) { int byt = 1; if ((chr & 0x80) == 0x00) { //1byte文字は何もしない([byt = 1]のまま) } else if ((chr & 0xE0) == 0xC0) { //2byte文字 byt = 2; } else if ((chr & 0xF0) == 0xE0) { //3byte文字 byt = 3; } else if ((chr & 0xF8) == 0xF0) { //4byte文字 byt = 4; } else if ((chr & 0xFC) == 0xF8) { //5byte文字 byt = 5; } else if ((chr & 0xFE) == 0xFC) { //6byte文字 byt = 6; } return byt; }
実行結果
実行結果はこんな感じ。流れるメッセージが表示された。
終わりに
今回は流れるメッセージが表示ができるようになった。今のままだと長い文章を表示させようとするとメッセージボックスを突き抜けて表示されてしまうので次は折り返しや改行、改ページの辺りを実装しようと思う。
ステップ関数・シグモイド関数
今回も引き続き、ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装という本から引用させてもらって、ステップ関数・シグモイド関数をC言語で実装してみようと思う。
実装
まず、ステップ関数を実装してみる。ステップ関数とは、入力が0を超えたら1を出力しそれ以外は0を出力する関数である。
本では視覚的に見るためにグラフを表示していたので、今回の実装はgnuplot
というフリーの描画ソフトをC言語から呼び出す形で使用してグラフを表示させていく。
gnuplot
については、以下を参考にさせていただいた。
gnuplotの基本的な使い方
gnuplotコマンド集
C言語からgnuplotを操作する
では、以下にソースを示す。
step.c
#include <stdio.h> #include <stdlib.h> #include <math.h> int array_range(double, double, double, double *); int step_function(double *, int *, int); int plot_graph(double *, int *, int); int main(int argc, char *argv[]) { double min = atof(argv[1]); double max = atof(argv[2]); double step = atof(argv[3]); double *array; int *y; int element = (fabs(min)+fabs(max))/step; array = malloc(sizeof(double) * element); y = malloc(sizeof(int) * element); array_range(min, max, step, array); step_function(array, y, element); plot_graph(array, y, element); free(y); free(array); return 0; } int array_range(double min, double max, double step, double *array) { int i = 0; double x = min; printf("%f %f %f\n", min, max, step); while (x <= max) { array[i] = x; x = x + step; i++; } return 0; } int step_function(double *x, int *y, int element) { int i; for (i=0;i<=element;i++) { if (x[i] > 0) { y[i] = 1; } else { y[i] = 0; } } return 0; } int plot_graph(double *array, int *y, int element) { int i; FILE *gp; gp = popen("gnuplot -persist", "w"); fprintf(gp, "plot '-' with lines linetype 1 title \"step\"\n"); for (i=0;i<element;i++) { fprintf(gp, "%f %d\n", array[i], y[i]); } fprintf(gp,"e\n"); pclose(gp); return 0; }
./step -5.0 5.0 0.1
のように実行すると、-5.0から5.0までの範囲を0.1刻みで配列を作ってステップ関数の実行結果を配列として返しプロットしてグラフを表示する。結果はこんな感じになる。
次に、シグモイド関数を実装する。シグモイド関数はニューラルネットワークでよく使われる関数だそうでステップ関数と違って滑らかな非線形関数となっている。
では、以下にソースを示す。注意点としてはシグモイド関数の実装にmath.h
のexp関数
を使用しているのでコンパイル時にgcc -g -o sigmoid sigmoid.c -lm
のように-lm
オプションをつける必要がある。
sigmoid.c
#include <stdio.h> #include <stdlib.h> #include <math.h> int array_range(double, double, double, double *); int sigmoid_function(double *, double *, double, int); int plot_graph(double *, double *, int); int main(int argc, char *argv[]) { double min = atof(argv[1]); double max = atof(argv[2]); double step = atof(argv[3]); double gain = atof(argv[4]); double *array; double *result; int element = (fabs(min)+fabs(max))/step; array = malloc(sizeof(double) * element); result = malloc(sizeof(double) * element); array_range(min, max, step, array); sigmoid_function(array, result, gain, element); plot_graph(array, result, element); free(result); free(array); return 0; } int array_range(double min, double max, double step, double *array) { int i = 0; double x = min; printf("%f %f %f\n", min, max, step); while (x <= max) { array[i] = x; x = x + step; i++; } return 0; } int sigmoid_function(double *array, double *result, double gain, int element) { int i; for (i=0;i<=element;i++) { result[i] = 1.0 / (1.0 + exp(-gain * array[i])); printf("%f %f\n", array[i], result[i]); } return 0; } int plot_graph(double *array, double *y, int element) { int i; FILE *gp; gp = popen("gnuplot -persist", "w"); fprintf(gp, "plot '-' with lines linetype 1 title \"step\"\n"); for (i=0;i<element;i++) { fprintf(gp, "%f %f\n", array[i], y[i]); } fprintf(gp,"e\n"); pclose(gp); return 0; }
./sigmoid -5.0 5.0 0.1 1.0
のように実行すると、-5.0から5.0までの範囲を0.1刻みで配列を作ってシグモイド関数の実行結果を配列として返しプロットしてグラフを表示する。ちなみに、第4引数の1.0
は標準シグモイド関数のgain値。結果はこんな感じになる。
所感
今回はステップ関数とシグモイド関数を実装してみた。それっぽいグラフが出力できてひとまず満足している。活性化関数を使用することでパーセプトロンからニューラルネットワークの世界へと進むことができるそうなので引き続きC言語で実装しつつ勉強を進めたい。