[cub3d](16)door(문)구현하기
1️⃣ 목표
- 열고 닫을 수 있는 문을 구현하기
- 움직이는 sprite를 구현하기
2️⃣ 문(door)구현하기
- 1개의 문(door)을 랜더링하기 위해서 따로 코드를 구현하는 것은 비효율적입니다. 그렇기 때문에 기존의
3D벽 을 랜더링하는 코드를 재활용할 계획입니다.
(1) 벽 심볼(symbol)등록하기
- 벽을 구분하기 위해서는 문(door)만의 심볼(symbol)이 있어야 합니다.
3
으로 지정해 주었습니다. - 가로 벽만 구현할 예정입니다. 가로 벽의 맵규칙은 다음과 같습니다.
좌, 우
양옆에는 반드시벽
이 있어야 합니다.위, 아래
로는 반드시 지나다닐 수 있는 길이 있어야 합니다.
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
값을 이용하면 됩니다.
< 문이 닫혔을 때 >
< 문이 열렸을 때 >
(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
이면 실내벽텍스쳐가 배정됨)
< 바뀐 실내의 모습 >
(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개의 텍스쳐 를 번갈아 노출되도록하는 예시를 보여드리겠습니다.- T_S: 첫번째 에니메이션 텍스쳐
- T_ST: 두번째 애니메이션 텍스쳐
- T_SU: 세번째 애니메이션 텍스쳐
- 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->time
을 1씩 증가시켜 주며god->time
을 매 프레임마다ANIMATE_DELAY
값으로 나눈 나머지값을 확인하고 지정한 값과 같을 경우다음 심볼
로 변경해주는 원리 입니다.
- 위와같이
4컷의 sprite 가 지정해준 시간(ANIMATE_DELAY)에 따라 반복적으로 출력됩니다.