ゲームを作りたい!

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

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かな。 ただ、動かしてて気になるのが猫の向きがずっと右向きなこと。 次はとりあえず、猫に進行方向を向かせようと思う。