[cub3d](6)player 움직임 구현하기(삽질의 과정)



1️⃣ 목표

  • 이전에 mlx_hook함수를 이용하여 키값을 입력받는데 성공하였습니다. 하지만 입력받은 키값을 이용하여 동작을 구현하는데 실패하였습니다.
  • 이번에 입력받은 키값을 이용하여 player의 동작을 구현하는 것이 목표입니다.


2️⃣ mlx_hook_loop함수 이용

  • 기존에 mlx_hook을 이용하여 키값을 입력받았습니다. 메인함수에서 사용된 mlx_loop함수가 그러한 키값을 계속해서 업데이트 해줄 것을 기대했었습니다.
  • 하지만 기대한 것 처럼 동작하지 않았습니다.
  • 다행히 mlx_hook_loop함수라는 이벤트에 대한 루프함수가 존재했습니다.

(1) mlx_hook_loop 매개변수

< mlx_hook_loop함수 원형 >

mlx_hook_loop

  • 첫번째 인자mlx포인터를 넣어줍니다.
  • 두번째 인자mlx_hook함수의 매동작마다 업데이트될 함수의 포인터를 넣어줍니다.
  • 세번째 인자로 두번째 인자로 넣어준 함수의 인자(param)를 넣어줍니다. 하지만 인자(param)가 여러개일 경우 구조체형태로 넘겨주어야 합니다.
  • 이번기회에 모든 변수를 담는 t_god god이라는 구조체를 만들어 주었습니다. (이전에 구현한 코드들이 약간씩 수정되지만 과정은 생략)

< t_god 구조체 >

typedef struct s_god {
    t_player player;
    t_img    img;
    void     *mlx;
    void     *win;
} t_god;

(2) ft_loop함수 구현

  • mlx_loop_hook함수의 두번째 인자값으로 넣어줄 함수를 구현했습니다.
int     ft_loop(t_god *god)
{
    draw_ray(god, &(god->player), &(god->img));
    return (0);
}
  • player를 그려주는 함수인 draw_ray함수를 이 루프 함수에 넣어서 관리하도록 하였습니다.
  • 이제 키값을 입력받을 때 마다 draw_ray함수가 호출되어 player값을 새롭게 그려줄 것입니다.


3️⃣ 이전player가 남아있는 문제 발생

(1) 문제

  • 지금까지의 과정을 통해 키값player동작에 적용이 잘되었습니다.
  • 하지만 다음과 같이 이전 player가 그대로 남아 있습니다.

< 이동 전 >

base_player

< 이동 후 >

preplayer_remain_problem

(2) 문제 해결

  • mlx_loop_hook함수의 동작원리를 파악하고 나니 이러한 문제를 해결하는 것은 어렵지 않았습니다.
  • 이전에 구현했던 지도를 그려주는 함수render_map함수를 mlx_loop_hook함수속에서 동작하도록 구현하면 됐습니다.
int     ft_loop(t_god *god)
{
    render_map(god, &(god->img));
    /* 코드 생략 */
}

< 이동 전 >

base_player

< 이동 후 >

after_player




4️⃣ 회전방향 적용하기

  • 기존에는 단순하게 상하좌우로 움직이도록 코드를 구현하였습니다.
  • 하지만 최종적으로 3D로 화면을 봤을때 바라보는 각도를 조절해줄 수 있어야합니다. 기존의 좌, 우움직이는 방향키를 바라보는 각도를 변경하는 키로 바꿀계획입니다.

(1) t_key key 구조체 만들기

  • 다음과 같이 t_key key구조체를 만들어줬으며 각방향별로 입력됐을 때: 1, 입력이 안됐을 때 0의 값을 가지도록 했습니다.
typedef struct s_key {
    int up;
    int down;
    int right;
    int left;
} t_key;

/* t_god 구조체에 추가 */
typedef struct s_god {
    /* 코드 생략 */
    t_key    key;
} t_god;

(2) key_press함수 수정

  • t_key key변수에 키값을 조정해주도록 key_press함수를 수정 해주었습니다.
int        key_press(int keycode, t_key *key)
{
    if (keycode == KEY_A)
        key->left = 1;
    if (keycode == KEY_D)
        key->right = 1;
    if (keycode == KEY_W)
        key->up = 1;
    if (keycode == KEY_S)
        key->down = 1;
    if (keycode == KEY_ESC)
        exit(0);
    
    return (0);
}

(3) player요소 추가 및 player_init함수 내용추가

typedef struct s_player {
    double x;
    double y;
    int thickness;
    double rotationAngle;
    double walkSpeed;
    double turnSpeed;
} t_player;
  • rotationAngle: 바라보는 각도
  • walkSpeed: 움직이는 속도
  • turnSpeed: 바라보는 각도의 조정속도
  • player의 위치를 나타내는 x, y변수는 좀 더 정확하게 계산하기 위해 double형으로 수정해 주었습니다.
void player_init(t_player *player)
{
    player->x = WINDOW_WIDTH / 2;
    player->y = WINDOW_HEIGHT / 2;
    player->thickness = PLAYER_THICKNESS;
    player->rotationAngle = PI / 2;
    player->walkSpeed = 1;
    player->turnSpeed = 4 * (PI / 180);
}
  • player을 초기화해주는 함수도 수정해 주었습니다.

(4) update_player함수 구현

int update_player(t_god *god)
{
    double turnDirection = 0;
    double walkDirection = 0;
    double newPlayerX;
    double newPlayerY;

    if (god->key.left == 1)
        turnDirection = -1;
    if (god->key.right == 1)
        turnDirection = 1;
    if (god->key.up == 1)
        walkDirection = 1;
    if (god->key.down == 1)
        walkDirection = -1;

    god->player.rotationAngle += turnDirection * god->player.turnSpeed;
    int moveStep = walkDirection * god->player.walkSpeed;
    newPlayerX = god->player.x + moveStep * cos(god->player.rotationAngle);
    newPlayerY = god->player.y + moveStep * sin(god->player.rotationAngle);

    if (!is_wall(newPlayerX, newPlayerY))
    {
        god->player.x = newPlayerX;
        god->player.y = newPlayerY;
    }
    return (0);
}
  • 예전에 Pikuma강의를 통해 배웠던 함수를 응용해서 만들어 주었습니다. 비슷하기 때문에 자세한내용은 이전의 포스트를 참고하면됩니다.
  • 벽을 통과하지 못하게 하기위해서 is_wall의 함수를 다음과 같이 구현하였습니다.
int is_wall(double x, double y)
{
	int xX;
	int yY;

	if (x < 0 || x > WINDOW_WIDTH || y < 0 || y > WINDOW_HEIGHT) {
            return (1);
    }
    xX = floor(x / TILE_SIZE);
    yY = floor(y / TILE_SIZE);
    return mini_map[yY][xX] != 0;
}

(5) draw_player함수 수정

  • player의 좌표값을 나타내는 x, y변수의 자료형이 int에서 double이 되는 바람에 x, y의 값이 소수값이 되면 제대로 그려주지 못하는일이 발생하였습니다.
  • 그래서 draw_player함수 내부에서 다음과 같이 (int)형으로 강제 케스팅해주었습니다.
/* 수정 전 */
img->data[(int)(MINI_SCALE * (WINDOW_WIDTH * (player->y + row) + (player->x + col)))] = 0x0000FF;

/* 수정 후 */
img->data[(int)(MINI_SCALE * (WINDOW_WIDTH * ((int)player->y + row) + ((int)player->x + col)))] = 0x0000FF;
  • 수정 후 정상적으로 작동이 되었습니다.


5️⃣ 아쉬운 부분(보완해야할 점)

  • player의 좌표값인 x, y값은 double형으로 계산이 되어집니다. 하지만 player를 그리는 부분에서 어쩔수없이 int형으로 강제 케스팅을 하였습니다.
  • 그렇다보니 실제로 게임을 실행해보면 바라보는 각도가 조금이라도 변경될 경우 똑바르게 전진을 못하는 모습이 보여졌습니다.
  • 아직 어떤식으로 해결해야할지 감이 잡히지않지만 이부분에 대해서 다시 한번 고민 해봐야할 것 같습니다.
  • 추후에 광선(ray)을 구현하여 다시 확인해본결과 해상도가 떨어져서 정확한 직선을 구현하지는 않지만 의도된대로 똑바로 전진하는 것을 확인할 수 있었습니다.




© 2021.02. by kirim

Powered by kkrim