ゲームを作りたい!

プログラミングをしているからには、いつかはゲームを作ってみたい。勉強したことを備忘録的に綴るBLOG。

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のほうのソースが結構先行して しまっている。

実行結果

宝箱を開けられるようになった。

f:id:K38:20191128053236g:plain
宝箱を開ける

終わりに

宝箱の状態を保持する方法がなかなか思いつかなくて結構苦戦したが、とりあえず実装できてよかった。
今回は宝箱の開閉部分だけをつくったが、最終的には取得したアイテムを保持して使うところまで実装していきたい。
次回は、扉を開けられるようにしたいと思う。