[cub3d](16)door(문)구현하기



1️⃣ 목표

  1. 열고 닫을 수 있는 문을 구현하기
  2. 움직이는 sprite를 구현하기


2️⃣ 문(door)구현하기

  • 1개의 문(door)을 랜더링하기 위해서 따로 코드를 구현하는 것은 비효율적입니다. 그렇기 때문에 기존의 3D벽을 랜더링하는 코드를 재활용할 계획입니다.

(1) 벽 심볼(symbol)등록하기

  • 벽을 구분하기 위해서는 문(door)만의 심볼(symbol)이 있어야 합니다. 3으로 지정해 주었습니다.
  • 가로 벽만 구현할 예정입니다. 가로 벽의 맵규칙은 다음과 같습니다.
    1. 좌, 우양옆에는 반드시 이 있어야 합니다.
    2. 위, 아래로는 반드시 지나다닐 수 있는 길이 있어야 합니다.
int set_door(t_god *god, int row, int col)
{
    if (god->parse.map[row][col - 1] != '1' || god->parse.map[row][col + 1] != '1')
        return (ERROR);
    if (ft_strchr("05", god->parse.map[row - 1][col]) == NULL
        || ft_strchr("05", god->parse.map[row + 1][col]) == NULL)
        return (ERROR);
    return (SUCCESS);
}
  • 위의 식을 파싱체크관련 함수안에 조건문으로 추가해 주면됩니다. (5는 실내통로로 구현할 예정)
else if (value == '3')
{
    if (is_space_around_position(god, row, col) == ERROR || set_door(god, row, col) == ERROR)
        return (exit_error(god, ERROR, "door error!"));
}
  • 위와 같이 파싱체크관련 함수안에서 맵의 요소가 3일경우 처리하도록 추가하였습니다.
  • 당연히 의 상하좌우로 빈공간이 올 수 없으므로 is_space_around_position()으로 체크해주었습니다.


(2) is_door함수 구현하기

  • door(문)3D벽랜더링 매커니즘을 이용한다고 앞서 말했습니다.
  • 3D벽랜더링의 데이터를 결정하는 것은 ray(광선)입니다. 그렇기에 ray(광선)이 문(door)을 발견할 수 있도록 is_door함수를 구현해야 합니다.
int is_door(t_god *god, double x, double y)
{
    int xX;
	int yY;

    xX = (int)floor(x / TILE_SIZE);
    yY = (int)floor(y / TILE_SIZE);
    if (ft_strchr("3", god->map.map[yY][xX]) != NULL)
        return (CLOSE_DOOR);
    else if (ft_strchr("4", god->map.map[yY][xX]) != NULL)
        return (OPEN_DOOR);
    return (FALSE);
}
  • is_door은 여러코드에서 문을 탐색할 용도로 사용됩니다. player를 움직이는 코드는 참(열린 문), 거짓(닫힌 문)으로 구현해도 됩니다.
  • 하지만 ray(광선)벽을 그리는 코드벽의 존재 유무를 구분해 줘야 합니다. 그렇기 때문에 is_door()의 함수 값이 3가지(CLOSE_DOOR, OPEN_DOOR, FALSE)로 나오도록 만들어 줬습니다.


(3)문열고 닫기 구현

/* key_press()내부 */
if (keycode == KEY_1)
    key->open_door = TRUE;
if (keycode == KEY_2)
    key->open_door = FALSE;

/* key_release()내부 */
if (keycode == KEY_1)
    key->open_door = 2;
if (keycode == KEY_2)
    key->open_door = 2;
  • KEY_1을 누를때 변수가 TRUE(1), KEY_2를 누를때 FALSE(0)이 되도록했으며 응답이 없을시 변수가 2로 바뀌도록 설정했습니다. (release값을 1, 0값중 하나로 지정하면 문이 자동으로 열고 닫히는 경우가 생깁니다.)
if (god->rray[i].is_door == CLOSE_DOOR)
    god->ray.wall_direction = T_DO;
else if (god->rray[i].is_door == OPEN_DOOR)
    god->ray.wall_direction = T_DC;
  • 위의 코드처럼 적절히 key값을 이용하면 됩니다.

< 문이 닫혔을 때 >

two_sprite

< 문이 열렸을 때 >

bug_sprite




(4)실내를 다르게 꾸미기

  • 을 통과하여 실내로 들어오면 밖과 다른 벽, 천장과 바닥이 그려지게 할 예정입니다.
  • 실내 벽에 사용할 심볼과 텍스쳐는 위에서 을 추가했던 것 처럼 추가해 주면됩니다.
void    is_in_room(t_god *god)
{
    int x;
    int y;

    x = (int)floor(god->player.x / TILE_SIZE);
    y = (int)floor(god->player.y / TILE_SIZE);
    if (god->map.map[y][x] == '5')
        god->door.in_room = 4;
    else
        god->door.in_room = 0;
}
  • 위의 코드를 이용하여 5(실내 통로)일 경우 god->door.in_room의 값을 4로 지정해줍니다. (상, 하, 좌, 우의 바깥벽실내벽의 심볼이 4씩 차이나기 때문)
else if (god->rray[i].hit_vertical == TRUE && (god->player.x - god->rray[i].wall_hit_x) > 0)
    god->ray.wall_direction = T_WE + god->door.in_room;
else if (god->rray[i].hit_vertical == TRUE && (god->player.x - god->rray[i].wall_hit_x) < 0)
    god->ray.wall_direction = T_EA + god->door.in_room;
else if (god->rray[i].hit_vertical == FALSE && (god->player.y - god->rray[i].wall_hit_y) > 0)
    god->ray.wall_direction = T_NO + god->door.in_room;
else
    god->ray.wall_direction = T_SO + god->door.in_room;
  • 위에 지정해준 god->door.in_room의 변수는 3D벽을 랜더링하기전 초기화해주는 함수내부에서 텍스쳐를 지정해주는 코드에서 사용됩니다. (0이면 그대로 4이면 실내벽텍스쳐가 배정됨)

< 바뀐 실내의 모습 >

two_sprite


(5)문안에서 시야가리기

  • 문의 크기는 TILE_SIZE x TILE_SIZE입니다. 그렇기 때문에 player가 문 안에있는 경우가 생깁니다.
  • 하지만 is_in_room함수를 이용하여 서있는 위치에 따라 텍스쳐가 바뀌도록 설정하였습니다. 정확히 문인 지점에서 바깥과 실내를 동시에 바라봤을 때 바깥과 실내로 구분되게 랜더링되도록 구현하는 것은 까다롭습니다.
  • 그렇기떄문에 아이에 문안에 있을땐 시야를 차단하도록 했습니다.
void    is_in_door(t_god *god)
{
    if (is_door(god, god->player.x, god->player.y) == OPEN_DOOR)
        for (int row = 0; row < god->map.window_height; row++)
            for (int col = 0; col < god->map.window_width; col++)
                if (god->img.data[god->map.window_width * row + col] == NO_COLOR)
                    god->img.data[god->map.window_width * row + col] = 0x000000;
}
  • 정확히 문의 위치에서는 0x000000(검은색)으로 표시되도록 했습니다.


3️⃣ 움직이는 sprite구현하기

  • 움직이는 sprite의 원리는 sprite에 사용할 텍스쳐를 여러개 이용하여 일정시간 간격으로 바꿔주면 됩니다.
  • 다음과 같이 4개의 텍스쳐를 번갈아 노출되도록하는 예시를 보여드리겠습니다.
    1. T_S: 첫번째 에니메이션 텍스쳐
    2. T_ST: 두번째 애니메이션 텍스쳐
    3. T_SU: 세번째 애니메이션 텍스쳐
    4. T_SV: 네번째 애니메이션 텍스쳐
god->time++;
while (god->sprite[++i].exist == TRUE && i < SPRITE_COUNT)
{
    if (god->time % ANIMATE_DELAY == 0)
    {
        if (god->sprite[i].symbol == T_S)
            god->sprite[i].symbol = T_ST;
        else if (god->sprite[i].symbol == T_ST)
            god->sprite[i].symbol = T_SU;
        else if (god->sprite[i].symbol == T_SU)
            god->sprite[i].symbol = T_SV;
        else if (god->sprite[i].symbol == T_SV)
            god->sprite[i].symbol = T_S;
    }
}
  • 위의 코드의 핵심은 매 프레임마다 god->time1씩 증가시켜 주며 god->time매 프레임마다 ANIMATE_DELAY값으로 나눈 나머지값을 확인하고 지정한 값과 같을 경우 다음 심볼로 변경해주는 원리 입니다.
two_sprite
bug_sprite
two_sprite
bug_sprite
  • 위와같이 4컷의 sprite지정해준 시간(ANIMATE_DELAY)에 따라 반복적으로 출력됩니다.




© 2021.02. by kirim

Powered by kkrim