[cub3d](10)서브젝트내용대로 방향기 구현하기



1️⃣ 목표

  • 이번의 목표는 다음과 같습니다.
  1. 'A', 'D'키는 기존에 바라보는 각도를 바꾸는 키로 구현, 이것을 단순히 왼쪽과 오른쪽으로 이동하는 키로 수정하기
  2. 바라보는 각도좌, 우방향키가 대신하도록 바꾸기
  3. 추가적으로 상, 하방향키를 이용하여 위 아래를 볼 수 있는 키도 구현하기
  4. 천장면과 바닥면색커스텀하는 함수를 추가하기


2️⃣ KEY값 추가하기

(1) 헤더파일에 정의

# define KEY_LEFT	(123)
# define KEY_RIGHT	(124)
# define KEY_UP 	(126)
# define KEY_DOWN	(125)

typedef struct s_key {
    int up;
    int down;
    int right;
    int left;
    int right_rotation;
    int left_rotation;
    int updown_sight;
} t_key;
  • 사용할 keycode들을 cub3d.h(헤더파일)에 정의해주었습니다.
  • 그리고 기존의 시야를 회전각을 조절하는 변수_rotation을 붙이도록 바꿔줬습니다.
  • 위아래시야를 결정할 updown_sight변수도 추가해 주었습니다.

(2) mlx_hook에 추가

< key_press >

if (keycode == KEY_RIGHT)
        key->right_rotation = FALSE;
    if (keycode == KEY_LEFT)
        key->left_rotation = FALSE;
    if (keycode == KEY_UP)
        key->updown_sight = 0;
    if (keycode == KEY_DOWN)
        key->updown_sight = 0;

< key_release >

if (keycode == KEY_RIGHT)
        key->right_rotation = FALSE;
    if (keycode == KEY_LEFT)
        key->left_rotation = FALSE;
    if (keycode == KEY_UP)
        key->updown_sight = 0;
    if (keycode == KEY_DOWN)
        key->updown_sight = 0;
  • 위아래시야의 조절하는 변수인 updown_sight도 단독으로 작동시키는 것 보다 key_release함수를 이용하는 편이 좀 더 동작이 매끄러웠습니다.


3️⃣ 동작 구현

(1) 좌, 우 동작구현

  • 기존의 player의 위치를 업데이트해주는 함수인 update_player함수에 구현을 추가해 주었습니다.
double moveside = 0.0;

if (god->key.left == TRUE)
{
    walkDirection = 1.0;
    moveside = PI / 2.0;
}
if (god->key.right == TRUE)
{
    walkDirection = -1.0;
    moveside = HALF_PI;
}

/* 코드 생략 */

newPlayerX = god->player.x + moveStep * cos(god->player.rotationAngle - moveside);
newPlayerY = god->player.y + moveStep * sin(god->player.rotationAngle - moveside);

if (!is_wall(newPlayerX, newPlayerY))
{
    god->player.x = newPlayerX;
    god->player.y = newPlayerY;
}
return (0);
  • 위의 코드를 보면 알듯이 현재위치에서 좌, 우로 이동하게 하려면 앞으로 이동할거리의 각도에 90도만큼의 각도변화를 주면됩니다.
  • HALF_PI라는 매크로로 90도를 표현했으며 walkDirectionmoveside의 부호는 방향에 맞춰서 잘 맞추면 됩니다. (90도의 각도변화를 준다는 것이 중요)

(2) 위, 아래시야 동작구현

  • 구현하기 이전에 물리적인 관점에서 생각해 본다면 위 아래시야 각도에 따라서 모니터 위치에 따른 광선의 길이가 달라질 것입니다.
  • 하지만 이전에 한 모니터상의 벽의 굴곡을 없애고 보정(어안렌즈효과 없앰)을 한 적이 있습니다.
  • 위, 아래시야 또한 보정을 주어 구현할 것입니다.

< 3D벽을 그리는 함수 내부의 반복문 >

for (int y = wall_top_pixel; y < wall_bottom_pixel; y++)
    for (int x = 0; x < WALL_STRIP_WIDTH; x++)
        if (god->img.data[WINDOW_WIDTH * y + (x + ray_num * WALL_STRIP_WIDTH)] == 0x111111)
            god->img.data[WINDOW_WIDTH * y + (x + ray_num * WALL_STRIP_WIDTH)] = color;
  • 기존에 3D벽을 그리는 반복문y축기준으로 wall_top_pixel부터 wall_bottom_pixel의 범위로 그려주는 반복문입니다.
  • 그렇기 때문에 단순히 wall_top_pixelwall_bottom_pixel동일한 수치만큼만 변경시켜주면 위 아래로 벽을 움직여줄 수있게됩니다.
  • god->player.updown_sight수치만큼만 변경해줬습니다. (어안렌즈 보정으로 오히려 쉽게 구현)
int wall_top_pixel = (WINDOW_HEIGHT / 2) - (wallStripHeight / 2) - god->player.updown_sight;
int wall_bottom_pixel = (WINDOW_HEIGHT / 2) + (wallStripHeight / 2) - god->player.updown_sight;
  • 다음과 같은 코드를 추가해주어 위 아래각도를 제한시켜주는 것이 좋습니다.
god->player.updown_sight += UPDOWNSPEED * god->key.updown_sight;
if (god->player.updown_sight > WINDOW_HEIGHT / 4)
    god->player.updown_sight = WINDOW_HEIGHT / 4;
if (god->player.updown_sight < - WINDOW_HEIGHT / 4)
    god->player.updown_sight = - WINDOW_HEIGHT / 4;
}

< 위쪽 최대시야 >

watch_up_direction_3D

< 아래쪽 최대시야 >

watch_down_direction_3D




4️⃣ 문제점 발견

(1) 버그 및 이유

  • 이번문제점이번 포스트에서 구현한 내용때매 생긴 버그가 아닙니다. 단지, 좌, 우동작키를 구현하고 실험하는 과정에서 발견되었습니다.
  • 방향각도의 수정없이(정확히 90도 유지) 한방향으로 벽에 부딪치면 시그멘트오류가 생기며 프로그램이 종료되었습니다.
  • 그 이유는 distance(거리)의 값이 0이되는데 distance변수로 나눠주는 코드들이 많기 때문에 결국 무한대 값이 계산과정에서 어딘가에 생기게 됩니다.
  • 이러한 버그기준각도가 정확히 90도의 배수일때만 발생하였는데, minilibx프로그램특성상 픽셀단위로 돌아가기 때문에 int값으로 보정되는 경우가 많습니다. 그렇기 때문에 다른 각도에서는 벽과 충돌하는 지점에서 distance(거리)의 값이 정확히 0이 나오는 경우는 거의 없었습니다.

(2) 문제해결

  • distance(거리)의 변수가 사용되는 곳의 함수에 조건문을 추가해주어 값을 보정해 주었습니다.
void    draw_line(t_god *god, double dx, double dy)
{
    /* 코드 생략 */
 
    max_value = fmax(fabs(dx), fabs(dy));
    if (max_value == 0)
        return ;
    dx /= max_value;
    dy /= max_value;
}
  • draw_line함수매개변수dx, dy(x, y좌표 각각의 거리)를 받아옵니다. 둘다 0이되어 max_value의 값이 0이 되는 것을 막아주었습니다.
if (god->ray.distance == 0)
    god->ray.distance = 0.1;
  • 위의 코드3D벽을 그려주는 함수render_3D_project_walls함수에 추가해주었습니다.


5️⃣ 천장, 바닥색 커스텀함수 구현

  • 천장, 바닥색을 커스텀해주는 함수의 구현은 간단합니다.
  • render_3D_project_walls함수에서 y축기준으로 wall_top_pixel부터 wall_bottom_pixel의 범위로 벽을 그려준다고 하였습니다.
  • 그렇기때문에 천장0에서 wall_top_pixel범위로 바닥 wall_bottom_pixel에서 WINDOW_HEIGHT의 범위로 그려주면 됩니다.

< draw_sky함수 >

int draw_sky(t_god *god, int ray_num, int wall_top_pixel, int color)
{
    for (int y = 0; y < wall_top_pixel; y++)
        for (int x = 0; x < WALL_STRIP_WIDTH; x++)
            if (god->img.data[WINDOW_WIDTH * y + (x + ray_num * WALL_STRIP_WIDTH)] == 0x111111)
                god->img.data[WINDOW_WIDTH * y + (x + ray_num * WALL_STRIP_WIDTH)] = color;
    return (0);
}

< draw_bottom함수 >

int draw_bottom(t_god *god, int ray_num, int wall_bottom_pixel, int color)
{
    for (int y = wall_bottom_pixel; y < WINDOW_HEIGHT; y++)
        for (int x = 0; x < WALL_STRIP_WIDTH; x++)
            if (god->img.data[WINDOW_WIDTH * y + (x + ray_num * WALL_STRIP_WIDTH)] == 0x111111)
                god->img.data[WINDOW_WIDTH * y + (x + ray_num * WALL_STRIP_WIDTH)] = color;
    return (0);
}
  • 아래와 같이 벽을 그려준 뒤에 각각의 함수를 호출하였습니다.
for (int y = wall_top_pixel; y < wall_bottom_pixel; y++)
    for (int x = 0; x < WALL_STRIP_WIDTH; x++)
        if (god->img.data[WINDOW_WIDTH * y + (x + ray_num * WALL_STRIP_WIDTH)] == 0x111111)
            god->img.data[WINDOW_WIDTH * y + (x + ray_num * WALL_STRIP_WIDTH)] = color;
draw_sky(god, ray_num, wall_top_pixel, SKY_COLOR);
draw_bottom(god, ray_num, wall_bottom_pixel, BOTTOM_COLOR);




© 2021.02. by kirim

Powered by kkrim