ゲームを作りたい!

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

SDL2でRPGゲームを作る 〜第三回 キャラクターに進行方向を向かせる〜

前回はマップとの当たり判定を実装して猫を草地に閉じ込めることができた。しかしながら、どの方向に動いても 常に右向きの状態で移動していた。今回はキャラクターに進行方向を向かせる方法を考えたい。

表示画像の変更

まず、今まで使用していた画像は以下。
f:id:K38:20181212005929j:plain
このように、16×64の右方向を向いている画像だけを使っていた。
今回は、上下左右どの方向も向けるように以下のように64×64の画像を用意した。
f:id:K38:20181212010111j:plain
この画像を使用して進行方向を向かせていく。
まず、前回から変更した部分のコードを示す。前回から大きくは変わっていないが キャラクターに向きの概念を導入した。

typedef enum {DOWN, LEFT, RIGHT, UP} DIRECTION;
DIRECTION direction = DOWN;

// event handling
SDL_Event e;
if ( SDL_PollEvent(&e) ) {
    if (e.type == SDL_QUIT){
        break;
    } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE){
        break;
    } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_UP){
        direction = UP;
        if (is_movable(mx, my - 1) == 0) {
            my = my - 1;
        }
    } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_DOWN){
        direction = DOWN;
        if (is_movable(mx, my + 1) == 0) {
            my = my + 1;
        }
    } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_RIGHT){
        direction = RIGHT;
        if (is_movable(mx + 1, my) == 0) {
            mx = mx + 1;
        }
    } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_LEFT){
        direction = LEFT;
        if (is_movable(mx - 1, my) == 0) {
            mx = mx - 1;
        }
    }
}
int character_animation(SDL_Renderer *renderer, DIRECTION direction, int mx, int my) {

    SDL_Texture *cat_image = NULL;

    load_image(renderer, &cat_image, "image/charachip/black_cat.bmp");

    int x = ((frame / animecycle) % 4) * 16;
    int y = direction * IMAGE_HEIGHT;

    SDL_Rect imageRect=(SDL_Rect){x, y, IMAGE_WIDTH, IMAGE_HEIGHT};
    SDL_Rect drawRect=(SDL_Rect){mx * GRID_SIZE, my * GRID_SIZE, 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;
}

キー入力時に上下左右の状態を取得し、character_animationで画像を表示するときに画像のY座標の位置を int y = direction * IMAGE_HEIGHT;
で求めている。
こうすることで、進行方向の画像を入力に合わせて表示することができた。

以下に、全コードをのせる。

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

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 ROW = 15;
const int COL = 20;
const int GRID_SIZE = 32;
int animecycle = 60;
int frame = 0;

typedef enum {DOWN, LEFT, RIGHT, UP} DIRECTION;

int load_image(SDL_Renderer *, SDL_Texture **, char *);
int character_animation(SDL_Renderer *, DIRECTION, int, int);
int draw_map(SDL_Renderer *);
int is_movable(int, int);

int map[300] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};


int main (int argc, char *argv[]) {

    SDL_Window *window = NULL;
    SDL_Renderer *renderer = 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_HEIG
    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);
    }

    int mx = 10;
    int my = 8;
    DIRECTION direction = DOWN;

    // main loop
    while (1) {
        SDL_RenderClear(renderer);
        draw_map(renderer);
        character_animation(renderer, direction, mx, my);
        SDL_RenderPresent(renderer);

        // event handling
        SDL_Event e;
        if ( SDL_PollEvent(&e) ) {
            if (e.type == SDL_QUIT){
                break;
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE){
                break;
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_UP){
                direction = UP;
                if (is_movable(mx, my - 1) == 0) {
                    my = my - 1;
                }
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_DOWN){
                direction = DOWN;
                if (is_movable(mx, my + 1) == 0) {
                    my = my + 1;
                }
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_RIGHT){
                direction = RIGHT;
                if (is_movable(mx + 1, my) == 0) {
                    mx = mx + 1;
                }
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_LEFT){
                direction = LEFT;
                if (is_movable(mx - 1, my) == 0) {
                    mx = mx - 1;
                }
            }
        }
    }

    IMG_Quit();
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);

    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 character_animation(SDL_Renderer *renderer, DIRECTION direction, int mx, int my) {

    SDL_Texture *cat_image = NULL;

    load_image(renderer, &cat_image, "image/charachip/black_cat.bmp");

    int x = ((frame / animecycle) % 4) * 16;
    int y = direction * IMAGE_HEIGHT;

    SDL_Rect imageRect=(SDL_Rect){x, y, IMAGE_WIDTH, IMAGE_HEIGHT};
    SDL_Rect drawRect=(SDL_Rect){mx * GRID_SIZE, my * GRID_SIZE, 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 draw_map(SDL_Renderer *renderer){

    SDL_Texture *grass_image = NULL;
    SDL_Texture *water_image = NULL;

    load_image(renderer, &water_image, "image/mapchip/water.bmp");
    load_image(renderer, &grass_image, "image/mapchip/grass.bmp");

    int x, y;
    for(y = 0;y < ROW;y++){
        for(x = 0; x < COL;x++){

            SDL_Rect imageRect=(SDL_Rect){0, 0, IMAGE_WIDTH, IMAGE_HEIGHT};
            SDL_Rect drawRect=(SDL_Rect){x * GRID_SIZE, y * GRID_SIZE, IMAGE_WIDTH*MAGNIFICATION, IMAGE_HEIGHT*MAGNIFICATION}

            if (map[y*COL+x] == 1) {
                SDL_RenderCopy(renderer, water_image, &imageRect, &drawRect);
            } else if (map[y*COL+x] == 0) {
                SDL_RenderCopy(renderer, grass_image, &imageRect, &drawRect);
            }

        }
    }

    SDL_DestroyTexture(grass_image);
    SDL_DestroyTexture(water_image);

    return 0;
}


int is_movable(int x, int y) {

    if ( x < 0 || x > COL - 1 || y  < 0 || y > ROW - 1) {
        return 1;
    }

    if (map[y*COL+x] == 1 ){
        return 1;
    }

    return 0;
}

実行結果

今回実行した結果は以下。
f:id:K38:20181212010533g:plain
このように猫に進行方向を向かせることができた。

終わりに

猫が進行方向を向いたことで移動はなんとなくいい感じになってきた。
しかしながら、ドラクエなどを思い返すと主人公は常に画面の真ん中に表示されてたように思う。
次回は、そのような表示をさせる方法について考えてみたいと思う。

SDL2でRPGゲームを作る 〜第二回 マップとの当たり判定を入れる〜

前回はマップを表示する方法を考えた。しかし、マップとの当たり判定がなかったためにキャラクターが画面外に 出て行けてしまっていた。今回は当たり判定を実装して、前回表示した草地にキャラクターを閉じ込めたいと思う。
あと、前回とりあえずマップを表示できていたがCPUをやたら食うといった現象が出ていた。 それにも、対処してみたのでコードは前回からだいぶ変わっている。しかし、今回のコードのほうが前回に比べて見やすくなっていると思う。

当たり判定を入れる

まずは、どうすればマップとの当たり判定ができるのかを考える。単純に考えればキャラクターが移動しようとしたマスが進んで良いマスかダメなマスかを判定して、移動して良い時だけその方向に進むようにできればいいということになる。ということは、キー入力があった時に進行方向のマスが進めるかを判断したうえで移動させてやれば良い。
この考え方に基づいて実装したコードが以下の部分になる。

SDL_Event e;
if ( SDL_PollEvent(&e) ) {
    if (e.type == SDL_QUIT){
        break;
    } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE){
        break;
    } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_UP){
        if (is_movable(mx, my - 1) == 0) {
            my = my - 1;
        }
    } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_DOWN){
        if (is_movable(mx, my + 1) == 0) {
            my = my + 1;
        }
    } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_RIGHT){
        if (is_movable(mx + 1, my) == 0) {
            mx = mx + 1;
        }
    } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_LEFT){
        if (is_movable(mx - 1, my) == 0) {
            mx = mx - 1;
        }
    }
}
int is_movable(int x, int y) {

    if ( x < 0 || x > COL - 1 || y  < 0 || y > ROW - 1) {
        return 1;
    }

    if (map[y*COL+x] == 1 ){
        return 1;
    }

    return 0;
}

進行方向のマスが進めるかどうかをis_movable関数で判断したうえで 移動を開始するようにする。
is_movable関数は、受け取ったマスの情報を元に、進めるのであれば「0」進めないのであれば「1」を返す。

以下にマップとの当たり判定を入れたコードを示す。
ちなみに、前回と大きく変更したのはdraw_map関数のforループの中で画像を読み込んでいたのを先に読み込んでからforループを回すようにした箇所。その他もちょこちょこいじっている。

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

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 ROW = 15;
const int COL = 20;
const int GRID_SIZE = 32;
int animecycle = 60;
int frame = 0;

int load_image(SDL_Renderer *, SDL_Texture **, char *);
int character_animation(SDL_Renderer *, int, int);
int draw_map(SDL_Renderer *);
int is_movable(int, int);

int map[300] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
 
 
int main (int argc, char *argv[]) {
 
    SDL_Window *window = NULL; 
    SDL_Renderer *renderer = 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);
    }

    int mx = 10;
    int my = 8;

    // main loop
    while (1) {
        SDL_RenderClear(renderer);
        draw_map(renderer);
        character_animation(renderer, mx, my);
        SDL_RenderPresent(renderer);

        // event handling
        SDL_Event e;
        if ( SDL_PollEvent(&e) ) {
            if (e.type == SDL_QUIT){
                break;
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE){
                break;
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_UP){
                if (is_movable(mx, my - 1) == 0) {
                    my = my - 1;
                }
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_DOWN){
                if (is_movable(mx, my + 1) == 0) {
                    my = my + 1;
                }
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_RIGHT){
                if (is_movable(mx + 1, my) == 0) {
                    mx = mx + 1;
                }
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_LEFT){
                if (is_movable(mx - 1, my) == 0) {
                    mx = mx - 1;
                }
            }
        }
    }

    IMG_Quit();
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);

    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 character_animation(SDL_Renderer *renderer, int mx, int my) {

    SDL_Texture *cat_image = NULL;

    load_image(renderer, &cat_image, "image/charachip/walkcat.bmp");

    int x = ((frame / animecycle) % 4) * 16;

    SDL_Rect imageRect=(SDL_Rect){x, 0, IMAGE_WIDTH, IMAGE_HEIGHT};
    SDL_Rect drawRect=(SDL_Rect){mx * GRID_SIZE, my * GRID_SIZE, 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 draw_map(SDL_Renderer *renderer){

    SDL_Texture *grass_image = NULL;
    SDL_Texture *water_image = NULL;

    load_image(renderer, &water_image, "image/mapchip/water.bmp");
    load_image(renderer, &grass_image, "image/mapchip/grass.bmp");

    int x, y;
    for(y = 0;y < ROW;y++){
        for(x = 0; x < COL;x++){

            SDL_Rect imageRect=(SDL_Rect){0, 0, IMAGE_WIDTH, IMAGE_HEIGHT};
            SDL_Rect drawRect=(SDL_Rect){x * GRID_SIZE, y * GRID_SIZE, IMAGE_WIDTH*MAGNIFICATION, IMAGE_HEIGHT*MAGNIFICATION}

            if (map[y*COL+x] == 1) {
                SDL_RenderCopy(renderer, water_image, &imageRect, &drawRect);
            } else if (map[y*COL+x] == 0) {
                SDL_RenderCopy(renderer, grass_image, &imageRect, &drawRect);
            }

        }
    }

    SDL_DestroyTexture(grass_image);
    SDL_DestroyTexture(water_image);

    return 0;
}


int is_movable(int x, int y) {

    if ( x < 0 || x > COL - 1 || y  < 0 || y > ROW - 1) {
        return 1;
    }

    if (map[y*COL+x] == 1 ){
        return 1;
    }

    return 0;

}

実行結果

今回実行した結果は以下。
f:id:K38:20181206000301g:plain
わかりづらいと思うが猫を海に入らないようにするようにできた。

終わりに

今回はマップとの当たり判定の方法を考えてみた。 他に進めない場所を作るにはis_movable関数に条件を追加していけば良いのでマップ上の動作はこれでOKかな。 ただ、動かしてて気になるのが猫の向きがずっと右向きなこと。 次はとりあえず、猫に進行方向を向かせようと思う。

SDL2でRPGゲームを作る 〜第一回 マップを表示する〜

前回の投稿から間が開いてしまったが、その間にどんなゲームを作りたいかをつらつらと考えてみた。
自分がどんなゲームをしていたかを思い返すと、ドラクエやFFといったRPGをよくやっていたように思う。
クロノ・トリガーなんかは何周したかわからないくらいプレイした。あの、細部まで作りこまれたゲームは、やるたびに新しい発見があって全てを遊び尽くせた気がしていない。今プレイしなおしても新しい発見があるように思う。

・・・といったように、自分の中でゲームといったらRPGが王道のようだ。よって、RPGゲームを作っていこうと思う。
とは言っても、いきなりあんなすごいゲームが作れる気は全くしない。一歩ずつ実現方法を考えていこうと思う。

マップを表示する

さて、作ると言ってもなにから手を付けていけば良いのやらなのでなんとなくできそうなところから実装していく。
とりあえず、前回まででキャラクターの表示はできているので (-> 画像の表示, -> アニメーションの表示)、 今回はマップを表示する方法を考えようと思う。先に、今回実装したコードを載せておく。

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

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 ROW = 15; 
const int COL = 20; 
const int GRID_SIZE = 32; 
int animecycle = 5;
int frame = 0;

typedef enum {TRUE, FALSE} TRANSPARENT;

int load_image(SDL_Renderer *, SDL_Surface **, SDL_Texture **, char *, TRANSPARENT);
int character_animation(SDL_Renderer *, SDL_Surface **, SDL_Texture **, int, int);
int draw_map(SDL_Renderer *, SDL_Surface **, SDL_Texture **);

int map[300] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};


int main (int argc, char *argv[]) {

    SDL_Window *window = NULL;
    SDL_Renderer *renderer = NULL;
    SDL_Surface *image = NULL;
    SDL_Texture *image_texture = 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);
    }


    SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);

    int iw,ih;     
    SDL_QueryTexture(image_texture, NULL, NULL, &iw, &ih);

    // 猫の初期位置
    int mx = 10;
    int my = 8;

    // main loop
    while (1) {
        SDL_RenderClear(renderer);
        draw_map(renderer, &image, &image_texture);
        character_animation(renderer, &image, &image_texture, mx, my);
        SDL_RenderPresent(renderer);

        // event handling
        SDL_Event e;
        if ( SDL_PollEvent(&e) ) {
            if (e.type == SDL_QUIT){
                break;
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE){
                break;
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_UP){
                my = my - 1;
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_DOWN){
                my = my + 1;
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_RIGHT){
                mx = mx + 1;
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_LEFT){
                mx = mx - 1;
            }
        }
    }

    IMG_Quit();
    SDL_FreeSurface(image);
    SDL_DestroyTexture(image_texture);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);

    SDL_Quit();

    return 0;

}

int load_image(SDL_Renderer *renderer, SDL_Surface **image, SDL_Texture **image_texture, char *filename, TRANSPARENT status)

    int flags=IMG_INIT_JPG|IMG_INIT_PNG;
    int initted=IMG_Init(flags);

    if(initted&flags != flags) {
        printf("IMG_Init: JPGとPNGの読み込みの初期化に失敗した!\n");
        printf("IMG_Init: %s\n", IMG_GetError());
    }

    // 画像の読み込み
    *image = IMG_Load(filename);
    if(!*image) {
        printf("IMG_Load: %s\n", IMG_GetError());
        return 1;
    }

    // 透過色の設定
    if (status == TRUE) {
        SDL_SetColorKey( *image, SDL_TRUE, SDL_MapRGB((*image)->format, 255, 0, 255));
    }

    *image_texture = SDL_CreateTextureFromSurface(renderer, *image);

    return 0;
}

int character_animation(SDL_Renderer *renderer, SDL_Surface **image, SDL_Texture **image_texture, int mx, int my) {

    TRANSPARENT status = TRUE;

    load_image(renderer, &*image, &*image_texture, "image/charachip/walkcat.png", status);

    int x = ((frame / animecycle) % 4) * 16;
    SDL_Rect imageRect=(SDL_Rect){x, 0, IMAGE_WIDTH,IMAGE_HEIGHT};
    SDL_Rect drawRect=(SDL_Rect){mx * GRID_SIZE, my * GRID_SIZE, IMAGE_WIDTH*MAGNIFICATION, IMAGE_HEIGHT*MAGNIFICATION};

    SDL_RenderCopy(renderer, *image_texture, &imageRect, &drawRect);

    if (frame <= animecycle * 4) {
        frame += 1;
    } else{
        frame = 0;
    }


    return 0;
}

int draw_map(SDL_Renderer *renderer, SDL_Surface **image, SDL_Texture **image_texture){

    int x, y;
    TRANSPARENT status = FALSE;

    for(y = 0;y < ROW;y++){
        for(x = 0; x < COL;x++){

            if (map[y*COL+x] == 1) {
                load_image(renderer, &*image, &*image_texture, "image/mapchip/water.png", status);
            } else if (map[y*COL+x] == 0) {
                load_image(renderer, &*image, &*image_texture, "image/mapchip/grass.png", status);
            }

            SDL_Rect imageRect=(SDL_Rect){0, 0, IMAGE_WIDTH, IMAGE_HEIGHT};
            SDL_Rect drawRect=(SDL_Rect){x * GRID_SIZE, y * GRID_SIZE, IMAGE_WIDTH*MAGNIFICATION, IMAGE_HEIGHT*MAGNIFICATION}

            SDL_RenderCopy(renderer, *image_texture, &imageRect, &drawRect);

        }
    }

    return 0;
}

マップの草部分は以下の画像。
f:id:K38:20181129233704j:plain
海部分には以下の画像を用意した。
f:id:K38:20181129233746j:plain
マップのサイズは15×20(ROW:列、COL:行)で一次元配列で表現している。二次元配列を使っても良いのだが、一次元配列のほうが個人的に 扱いやすいのでそのようにした。二次元配列を一次元配列で扱うときは、 二次元配列 b[H][W] の要素 b[y][x] は,1次元配列 c[WH] の要素 c[yW + x] に対応するので

二次元配列 map[15][20] の要素 map[1, 1]は、1次元配列 map[15*20] の要素 map[y*20 + x] に対応する

のようにしてやればよい。(数字を当てはめると納得できると思う)

const int ROW = 15;
const int COL = 20;

int map[300] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
                1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};

配列mapを、じーっと見てみるとなんとなくマップっぽく見えると思う。
とりあえず、草地と海だけなので0と1だけでマップを構成している。山とか岩とか森とか砂漠とか表示したいときは違う数値を あてがってやれば良いはず。

const int GRID_SIZE = 32;

int draw_map(SDL_Renderer *renderer, SDL_Surface **image, SDL_Texture **image_texture){

    int x, y;
    TRANSPARENT status = FALSE;

    for(y = 0;y < ROW;y++){
        for(x = 0; x < COL;x++){

            if (map[y*COL+x] == 1) {
                load_image(renderer, &*image, &*image_texture, "image/mapchip/water.png", status);
            } else if (map[y*COL+x] == 0) {
                load_image(renderer, &*image, &*image_texture, "image/mapchip/grass.png", status);
            }

            SDL_Rect imageRect=(SDL_Rect){0, 0, IMAGE_WIDTH, IMAGE_HEIGHT};
            SDL_Rect drawRect=(SDL_Rect){x * GRID_SIZE, y * GRID_SIZE, IMAGE_WIDTH*MAGNIFICATION, IMAGE_HEIGHT*MAGNIFICATION}

            SDL_RenderCopy(renderer, *image_texture, &imageRect, &drawRect);

        }
    }

    return 0;
}

今回一番重要なのがこのdraw_map関数。配列mapを見ながら草地と海を表示していくようになっている。
GRID_SIZEはマップ1マスのサイズを表していて16×16のサイズのマップチップを2倍にして使ってるので32が固定値で入っている。
drawRectに入れる座標は配列マップの位置×GRID_SIZEになっている。
配列マップにしたがってタイルを敷いてるようなイメージを持つとわかりやすいと思う。

実行結果

今回実行した結果は以下(gifにした際に画質がだいぶ落ちてしまったが雰囲気は感じられると思う)。
f:id:K38:20181129231824g:plain
無事にマップを表示できた。
しかしながら、当たり判定をしていないので、とうぜんながら海に入って画面外に出て行く動きができてしまう。

終わりに

今回はマップ表示の方法を考えてみた。とりあえず、マップの上を動けるようになったのでRPGゲーム作りの ひとまず一歩を踏み出したかな。 次回は当たり判定を導入して草地のみ動けるようにしようと思う。
あと、今回書いたコードだとマップを書くたびに画像をload_image関数で読み込んでいるためやたらとCPUを食っている。次回はその辺りも改善していきたい。

SDL2でマウス入力を検知する

今回はSDL2を使用してマウス入力の検知をする。 キーボードに次いでマウスからの入力ができればゲームの操作性が色々と考えられるようになって来るから楽しくなってくるよね。 では、早速マウス入力の検知はどのようにすればできるの調べていこう。

マウスイベントを検知する

まず、マウスイベントを検知するためには次の関数を使用するようだ。
-> SDL_Point
-> SDL_MouseButtonEvent
詳細は、リンク先を参照してもらうことにして、ざっくり説明すると
SDL_Pointを使用するとウィンドウ上のマウスの座標を得ることができて、
SDL_MouseButtonEventを使用することで右クリック左クリック等の識別ができる。

実にさらっとマウス入力の検知ができるようなので、前回のキーボート入力の検知のソース(-> ここを参照)をちょっと書き換えてマウス入力の検知を行った。

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

const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
const int IMAGE_WIDTH = 16;
const int IMAGE_HEIGHT = 16;
const int MAGNIFICATION = 4;

typedef enum {right, left} CONTROL;

int main (int argc, char *argv[]) {

    const int IMAGE_WIDTH_SENTER = IMAGE_WIDTH * MAGNIFICATION / 2;
    const int IMAGE_HEIGHT_SENTER = IMAGE_HEIGHT * MAGNIFICATION / 2;

    SDL_Window *window = NULL;
    SDL_Renderer *renderer = NULL;
    SDL_Surface *image = NULL;
    SDL_Texture *image_texture = NULL;

    // ウィンドウの位置
    SDL_Point window_position = {
        SDL_WINDOWPOS_CENTERED,
        SDL_WINDOWPOS_CENTERED
    };

    // ウィンドウのサイズ
    SDL_Point window_size = {SCREEN_WIDTH, SCREEN_HEIGHT};

    // マウスの座標    
    SDL_Point mouse_position = {IMAGE_WIDTH_SENTER, IMAGE_WIDTH_SENTER};

    //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( "KEY EVENT 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);
    }


    // 画像の読み込み
    image = IMG_Load("walkcat.bmp");
    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_SetRenderDrawColor(renderer, 200, 200, 200, 255);

    int iw,ih;
    SDL_QueryTexture(image_texture, NULL, NULL, &iw, &ih);


    int animecycle = 60;
    int frame = 0;

    CONTROL status = right;

    // main loop
    while (1) {

        int x = ((frame / animecycle) % 4) * 16;
        SDL_Rect imageRect=(SDL_Rect){x,0,IMAGE_WIDTH,IMAGE_HEIGHT};
        SDL_Rect drawRect=(SDL_Rect){mouse_position.x - (IMAGE_WIDTH_SENTER),
                                     mouse_position.y - (IMAGE_HEIGHT_SENTER),
                                     IMAGE_WIDTH*MAGNIFICATION,IMAGE_HEIGHT*MAGNIFICATION};

        SDL_RenderClear(renderer);
        if (status == right) {
            SDL_RenderCopy(renderer, image_texture, &imageRect, &drawRect);
        } else if (status == left) {
            SDL_RenderCopyEx(renderer, image_texture, &imageRect, &drawRect,0 , NULL ,SDL_FLIP_HORIZONTAL);
        }
        SDL_RenderPresent(renderer);

        if (frame <= animecycle * 4) {
            frame += 1;
        } else{
            frame = 0;
        }

        // event handling
        SDL_Event e;
        if ( SDL_PollEvent(&e) ) {
            if (e.type == SDL_QUIT){
                break;
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE){
                break;
            } else if (e.type == SDL_MOUSEBUTTONDOWN && e.button.button == SDL_BUTTON_RIGHT){
                // マウスのウィンドウ上の座標を得る
                SDL_GetMouseState(&mouse_position.x, &mouse_position.y);
                status = right;
            } else if (e.type == SDL_MOUSEBUTTONDOWN && e.button.button == SDL_BUTTON_LEFT){
                SDL_GetMouseState(&mouse_position.x, &mouse_position.y);
                status = left;
            }
        }
    }

    IMG_Quit();
    SDL_FreeSurface(image);
    SDL_DestroyTexture(image_texture);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);

    SDL_Quit();

    return 0;

}

このコードを実行すると、クリックした位置に画像が移動する。
また、右左どちらのクリックを検知しているかわかりやすくするために、
左クリックだと画像が左向き、右クリックだと画像が右向きになるようにしてみた。

マウス検知のために追加した部分

今回のマウス検知で新たに追加されたのは以下の部分。
SDL_Pointでウィンドウの位置やサイズを設定して、あとはSDL_MouseButtonEventを使用してクリックした箇所の座標を拾っている。

// ウィンドウの位置
SDL_Point window_position = {
    SDL_WINDOWPOS_CENTERED,
    SDL_WINDOWPOS_CENTERED
};

// ウィンドウのサイズ
SDL_Point window_size = {SCREEN_WIDTH, SCREEN_HEIGHT};

// マウスの座標    
SDL_Point mouse_position = {IMAGE_WIDTH_SENTER, IMAGE_WIDTH_SENTER};
// event handling
SDL_Event e;
if ( SDL_PollEvent(&e) ) {
    if (e.type == SDL_QUIT){
        break;
    } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE){
        break;
    } else if (e.type == SDL_MOUSEBUTTONDOWN && e.button.button == SDL_BUTTON_RIGHT){
        // マウスのウィンドウ上の座標を得る
        SDL_GetMouseState(&mouse_position.x, &mouse_position.y);
        status = right;
    } else if (e.type == SDL_MOUSEBUTTONDOWN && e.button.button == SDL_BUTTON_LEFT){
        SDL_GetMouseState(&mouse_position.x, &mouse_position.y);
        status = left;
    }
}

その他の追加した部分

以下の部分では画像の貼り付け先の左上のx座標・y座標を指定してるが、マウスから取得した座標そのままだと
クリックした位置の右下に画像が出力されるのでクリックした位置が画像の中央になるように位置合わせをしている。

SDL_Rect drawRect=(SDL_Rect){mouse_position.x - (IMAGE_WIDTH_SENTER),
                             mouse_position.y - (IMAGE_HEIGHT_SENTER),
                             IMAGE_WIDTH*MAGNIFICATION,IMAGE_HEIGHT*MAGNIFICATION};

以下の部分では今まで使ったことのない新たな関数を使用している。
これは、左クリックの時にキャラクターに左を向かせるために画像を反転させるために使用している。
詳細は以下のリンクで確認できる。
-> SDL_RenderCopyEx
-> SDL_RendererFlip

SDL_RenderClear(renderer);
if (status == right) {
    SDL_RenderCopy(renderer, image_texture, &imageRect, &drawRect);
} else if (status == left) {
    SDL_RenderCopyEx(renderer, image_texture, &imageRect, &drawRect, 0, NULL ,SDL_FLIP_HORIZONTAL);
}

実行結果

こんな感じでクリックに合わせて画像が移動する。
f:id:K38:20181113232916g:plain

終わりに

これで、だいたいこれからゲームを作るにあたって基本的な所は調べたかなと思う。
あとは、どんなゲームを作るかによって色々と調べることが変わるだろうからどんなゲーム作るか少し考えてみようかな。とりあえず、今回はこんな感じで終わります。それではまた次回。

SDL2でキーボード入力を検知する

今回はSDL2を使用してキーボード入力の検知をする。 キーボードからの入力ができるようになるとゲームっぽい何かが作れそうな気がしてくるよね。 では、早速キーボード入力の検知はどのようにすればできるの調べていこう。

キーイベントを検知する

-> ここ(キーボートイベントタイプ)-> ここ(キーコード)にイベントの種類が色々まとめてある。
また、イベントを検知するには-> この関数(SDL_PollEvent)を使用するようだ。 早速コードを書いてみた。

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

const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
const int IMAGE_WIDTH = 16;
const int IMAGE_HEIGHT = 16;
const int MAGNIFICATION = 4;

int main (int argc, char *argv[]) {
    SDL_Window* window = NULL;
    SDL_Renderer *renderer = NULL;
    SDL_Surface *image = NULL; 
    SDL_Texture *image_texture = 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( "KEY EVENT 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);
    }

    
    // 画像の読み込み
    image = IMG_Load("walkcat.bmp");
    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_SetRenderDrawColor(renderer, 200, 200, 200, 255);

    int iw,ih;     
    SDL_QueryTexture(image_texture, NULL, NULL, &iw, &ih);


    int animecycle = 60;
    int frame = 0;

    int mx = 0;
    int my = 0;
    // main loop
    while (1) {

        int x = ((frame / animecycle) % 4) * 16;
        SDL_Rect imageRect=(SDL_Rect){x,0,IMAGE_WIDTH,IMAGE_HEIGHT};
        SDL_Rect drawRect=(SDL_Rect){mx,my,IMAGE_WIDTH*MAGNIFICATION,IMAGE_HEIGHT*MAGNIFICATION};

        SDL_RenderClear(renderer);
        SDL_RenderCopy(renderer, image_texture, &imageRect, &drawRect);
        SDL_RenderPresent(renderer);

        if (frame <= animecycle * 4) {
            frame += 1;
        } else{
            frame = 0;
        }

        // event handling
        SDL_Event e;
        if ( SDL_PollEvent(&e) ) {
            if (e.type == SDL_QUIT){
                break;
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE){
                break;
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_RIGHT){
                mx = mx + IMAGE_WIDTH*MAGNIFICATION;
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_LEFT){
                mx = mx - IMAGE_WIDTH*MAGNIFICATION;
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_UP){
                my = my - IMAGE_WIDTH*MAGNIFICATION;
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_DOWN){
                my = my + IMAGE_WIDTH*MAGNIFICATION;
            }
        }
    }

    IMG_Quit();
    SDL_FreeSurface(image);
    SDL_DestroyTexture(image_texture);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);

    SDL_Quit();

    return 0;

}

前回のアニメーションのコード(-> ここを参照)に キーイベントを加えてキャラクターの位置を変えられるようにしてみた。前回のコードとの違いは以下になる。

    int animecycle = 60;
    int frame = 0;

    int mx = 0;
    int my = 0;
    // main loop
    while (1) {

        int x = ((frame / animecycle) % 4) * 16;    
        SDL_Rect imageRect=(SDL_Rect){x,0,IMAGE_WIDTH,IMAGE_HEIGHT};      
        SDL_Rect drawRect=(SDL_Rect){mx,my,IMAGE_WIDTH*MAGNIFICATION,IMAGE_HEIGHT*MAGNIFICATION};

        SDL_RenderClear(renderer);
        SDL_RenderCopy(renderer, image_texture, &imageRect, &drawRect);
        SDL_RenderPresent(renderer);

        if (frame <= animecycle * 4) {
            frame += 1;
        } else{
            frame = 0;
        }

        // event handling
        SDL_Event e;
        if ( SDL_PollEvent(&e) ) {
            if (e.type == SDL_QUIT){
                break;
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_ESCAPE){
                break;
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_RIGHT){
                mx = mx + IMAGE_WIDTH*MAGNIFICATION;
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_LEFT){
                mx = mx - IMAGE_WIDTH*MAGNIFICATION;
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_UP){
                my = my - IMAGE_WIDTH*MAGNIFICATION;
            } else if (e.type == SDL_KEYDOWN && e.key.keysym.sym == SDLK_DOWN){
                my = my + IMAGE_WIDTH*MAGNIFICATION;
            }
        }
    }

目新しくなったのは、whileループでアニメーションを表示しキーイベントを受け付けるようにしたところである。
今回追加したコードだとEscキーで処理を終了し矢印ボタンで表示されているキャラクターの位置が変化するようになっている。
キャラクターの位置を変えるのは単純に画像の大きさ分、x座標とy座標の位置を足したり引いたりしているだけ。
特に範囲を限定しているわけではないのでウィンドウ外にもキャラクターは移動できてしまうけど、
やりたかったのはキーボードからの検知だから、まぁOKってことにしよう。

実行結果

こんな感じでキャラクターの位置がキー入力に合わせて変化する。 f:id:K38:20181108234557g:plain

終わりに

SDL2を使うことで結構簡単にキーイベント入力の検知ができた。
次回はマウスからの入力を検知してみようかな。
そこまでできれば、とりあえずゲームを作っていく最低限は調べた感じになるかなぁ。

SDL2でアニメーションを表示する

今回はSDL2を使用してアニメーションを表示していこうと思う。
基本的には-> ここの記事のコードをベースにしている。

アニメーションさせる画像の準備

今回アニメーションに使う画像は以下である。
f:id:K38:20181031233811j:plain:w100
16×16の画像が4枚横につながっていて16×64の画像になっている。
これを、4分割に切り取って順番に表示させることでアニメーションになる。

ソースコードと画像ファイルの階層は以下のようになっている。

sdl2_test/
  |-animation.c
  |-walkcat.bmp

アニメーションさせる

今回は新たなSDL2の関数は使用していないので関数へのリンクは無し。
ただし、アニメーションさせるために導入した式と使用したSDL2の関数の説明を
次の項目で説明してみようと思う。
ちなみに、SDL_InitSDL_CreateWindowはIf文の入れ子にしないで同じ深さになるようにしてみた。

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

const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
const int IMAGE_WIDTH = 16; 
const int IMAGE_HEIGHT = 16; 
const int MAGNIFICATION = 4;

int main (int argc, char *argv[]) {
    SDL_Window* window = NULL;
    SDL_Renderer *renderer = NULL;
    SDL_Surface *image = NULL;
    SDL_Texture *image_texture = 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_HEIG
    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);
    }


    // 画像の読み込み
    image = IMG_Load("walkcat.bmp");
    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_SetRenderDrawColor(renderer, 200, 200, 200, 255);

    int iw,ih;     
    SDL_QueryTexture(image_texture, NULL, NULL, &iw, &ih);


    int animecycle = 60;
    int frame = 0;

    // main loop
    while (frame < 3000) {

        int x = ((frame / animecycle) % 4) * 16;
        SDL_Rect imageRect=(SDL_Rect){x,0,IMAGE_WIDTH,IMAGE_HEIGHT};
        SDL_Rect drawRect=(SDL_Rect){300,220,IMAGE_WIDTH*MAGNIFICATION,IMAGE_HEIGHT*MAGNIFICATION};

        SDL_RenderClear(renderer);
        SDL_RenderCopy(renderer, image_texture, &imageRect, &drawRect);
        SDL_RenderPresent(renderer);

        frame += 1;

    }

    IMG_Quit();
    SDL_FreeSurface(image);
    SDL_DestroyTexture(image_texture);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);

    SDL_Quit();

    return 0;

}

アニメーション部分の説明

アニメーションは、読み込んだ画像を次々と切り替えることで実現することができる。
これは、今回で言うと4分割した画像をなんらかのタイミングで切り替えて表示することができれば
アニメーションになるということだ。

その、画像の分割とタイミングを決めているのがこの部分。

    int animecycle = 60;
    int frame = 0;

    // main loop
    while (frame < 3000) {

        int x = ((frame / animecycle) % 4) * 16;
        SDL_Rect imageRect=(SDL_Rect){x,0,IMAGE_WIDTH,IMAGE_HEIGHT};
        SDL_Rect drawRect=(SDL_Rect){300,220,IMAGE_WIDTH*MAGNIFICATION,IMAGE_HEIGHT*MAGNIFICATION};

        SDL_RenderClear(renderer);
        SDL_RenderCopy(renderer, image_texture, &imageRect, &drawRect);
        SDL_RenderPresent(renderer);

        frame += 1;

    }

imageRectdrawRectは、SDL_RenderCopyに渡すSDL_Rect構造体になる。
SDL_Rect構造体とは、左上を基点とした長方形を定義する構造体のこと。
imageRectはコピー元のSDL_Rectで、drawRectはコピー先のSDL_Rectだ。
SDL_Rect構造体は、

int     x       長方形の左上のX座標
int     y       長方形の左上のY座標
int     w       長方形の幅
int     h       長方形の高さ

で構成されている。
よって、コピー元の画像を4分割してコピー先に渡すためには
x座標の位置を変えて画像を切り取っていけば良い。
今回使用している画像は一枚16×16の画像になるのでx座標の位置を
0, 16, 32, 48と変化させれば良いことになる。

x座標の位置を変化させるために使用している式がこの式。

int x = ((frame / animecycle) % 4) * 16;

(frame / animecycle) % 4の部分で、0〜3までの数値を算出している。
そこに16をかけることでx座標の位置がでる。
また、この式を使うとframeがanimecycle分増加することで次の画像へと移り変わるので
アニメーションの間隔を自分で設定することができる。
数値の切り替わりは以下のようになっている。

frame=0   (  0 / 60) % 4 = 0
frame=60  ( 60 / 60) % 4 = 1
frame=120 (120 / 60) % 4 = 2
frame=180 (180 / 60) % 4 = 3
frame=240 (240 / 60) % 4 = 0
frame=300 (300 / 60) % 4 = 1
frame=360 (360 / 60) % 4 = 2
frame=420 (420 / 60) % 4 = 3

以下同様に続く

以上が、アニメーションの説明になる。

もうひとつ説明して起きたいのがこの部分。

SDL_Rect drawRect=(SDL_Rect){300,220,IMAGE_WIDTH*MAGNIFICATION,IMAGE_HEIGHT*MAGNIFICATION};

ここで定義しているMAGNIFICATIONは画像の倍率。
というのも、SDL_RenderCopyはコピー先で指定された幅と高さに画像を自動で拡大縮小してくれる。
MAGNIFICATIONを定義することで、好きな倍率に設定できるようにした。
今回はMAGNIFICATIONの部分を4にしているので、実際の画像より4倍大きく拡張して表示を行っている。

実行結果

実行結果はこんな感じ。ちゃんと、アニメーションになっている。
f:id:K38:20181031234641g:plain:w300

終わりに

今回はSDL2を使用してアニメーションを表示してみた。
この部分をうまく関数化すれば色々使いまわせるようになるんだろうなぁと思ったり。
次回は、SDL2を使ったキーボード入力の検知の方法を調べようと思う。

どうでもいいんだけど説明するっていうのは難しい。
頑張ってまとめてみたけど、後で見返した時に頭に???が浮かびそう。
文章力も鍛えてかないと記事書くのがコード書くより時間かかってしまうなぁ。

SDL2で音を鳴らす

前回はウィンドウ上に画像を表示する方法を調べた。
今回はSDL2を使用して音を鳴らす方法を調べて行きたい。

音楽ファイルの準備

今回は、.oggファイルと.wavの2種類の音源を用意した。
.oggは、.mp3のような圧縮ファイルらしいが同じビットレートだと.mp3より音質がいいらしい。
.wavファイルは音を非常に高音質に録音できる形式で、音を録音する際の「原音」の形式としてよく使われてるとのこと。 詳細はググって見てもらえればと思う。
今回用意したファイルは-> 魔王魂というフリー素材を扱っているサイトからダウロードさせてもらった。
いずれは音楽も作りたいので調べたところ、フリーのDTMソフトである-> LMMS というソフトが良さそうだった。 今回を皮切りにしてDTMも少しずつ学んでいこうと思う。
LMMSを使用しての音源ファイルの作成については別記事でまとめながら覚えてていきたい。

音を鳴らす

では、さっそく、音を鳴らすためのコードを書いていこう。
ファイルの階層はこんな感じにしてある。

sdl2_test/
  |--sdl2_mixer.c
  |--jingle.wav
  |--piano.ogg

調べて書いたところ以下のようなソースになった。
音楽関連の用語の知識がないためにマニュアルを見てもいまいちピンと来なかったりするが
以下のソースでひとまず音を鳴らすことはできた。

#include <SDL2/SDL.h>
#include <SDL2/SDL_mixer.h>

#define WAV_PATH "jingle.wav"
#define MUS_PATH "piano.ogg"

Mix_Chunk *wave = NULL;
Mix_Music *music = NULL;


int main(int argc, char* argv[]){

        // Initialize SDL.
        if (SDL_Init(SDL_INIT_AUDIO) < 0)
                return -1;

        // Initialize SDL_mixer 
        if( Mix_OpenAudio( 22050, MIX_DEFAULT_FORMAT, 2, 4096 ) == -1 )
                return -1;

        // 効果音のロード 
        wave = Mix_LoadWAV(WAV_PATH);
        if (wave == NULL)
                return -1;

        // 音楽ファイルのロード
        music = Mix_LoadMUS(MUS_PATH);
        if (music == NULL)
                return -1;

        // 効果音を一度だけ再生
        if ( Mix_PlayChannel(-1, wave, 0) == -1 )
                return -1;

        // 音楽を無限ループで再生
        if ( Mix_PlayMusic( music, -1) == -1 )
                return -1;

        while ( Mix_PlayingMusic() ) ;

        Mix_FreeChunk(wave);
        Mix_FreeMusic(music);

        // quit SDL_mixer
        Mix_CloseAudio();

        return 0;
}

使用した関数のメモ。
-> Mix_Chunk
-> Mix_Music
-> Mix_OpenAudio
-> Mix_LoadWAV
-> Mix_LoadMUS
-> Mix_PlayChannel
-> Mix_PlayMusic
-> Mix_PlayingMusic
-> Mix_FreeChunk
-> Mix_FreeMusic
-> Mix_CloseAudio

コンパイル

gcc -g -o sdl2_mixer sdl2_mixer.c `sdl2-config --cflags --libs` -lSDL2_mixer

このような形でコンパイルを行う。
文字は-lSDL2_ttfを追加、画像の場合は-lSDL2_imageを追加、 音源の場合は-lSDL2_mixerを使用する。

実行結果

./sdl2_mixerで実行する。
無事に音を鳴らすことができた。

終わりに

わからない部分がまだ多いがひとまず音を鳴らすことができた。
次回は画像に戻ってアニメーションの表示を行ってみようと思う。