[42] So_long

KURTY·2023년 1월 9일
0

42_SEOUL

목록 보기
7/9

So_long

간단한 2D 게임 만들기


MiniLibx

MiniLibx는 그래픽라이브러리로 그래픽에 대한 지식 없이도 렌더링할 수 있게 해주는 라이브러리.

기본적으로 해당 라이브러리를 사용하기 위해선 해당 과제란에서 다운 받아서 사용한다.

#include "./mlx/mlx.h"

int main()
{
	void *mlx_ptr;
    void *win_ptr;
    
    mlx_ptr = mlx_init();
    win_ptr = mlx_new_window(mlx_ptr, 300, 300, "mlx_test");
    mlx_loop(mlx_ptr);
}

// 컴파일: cc -L./mlx -lmlx -framework OpenGL -framework AppKit main.c

사용 함수 설명

void *mlx_init(void)
  • 나의 소프트웨어와 OS의 디스플레이를 연결해주는 함수
  • 연결 실패시 NULL반환
void *mlx_new_window(void mlx_ptr, int size_x, int size_y, char *title)
  • 디스플레이에 새로운 윈도우를 띄우는 함수로 가로/세로 크기를 받아서 띄운다. (title은 이름)
int mlx_loop(void *mlx_ptr)
  • 띄운 창에서 키보드와 마우스 입력을 loop하며 기다린다.
  • 이벤트를 받기 위해 써야하는 함수
int mlx_hook(void win_ptr, int x_event, int x_mask, int (funct)(), void *param)
  • x_event는 mlx에 등록된 이벤트의 번호다.
  • funct_ptr는 이벤트 발생시 호출하고 싶은 함수를 가리키는 함수 포인터 입니다. 이 할당은 win_ptr에 의해 특정된 윈도우에만 적용된다.
  • x_mask는 사용하지 않는다.
  • param의 주소는 호출될 때마다 전달되고 필요한 매개 변수를 저장하는 데 사용해야 한다.
int	mlx_loop_hook(void *win_ptr, int (*funct_ptr)(), void *param)
  • event가 발생하지 않았을 때에도 계속해서 호출이 된다.
void *mlx_xpm_file_to_image(void *mlx_ptr, char *filename, int *width, int *height)
  • xpm file의 파일명을 인자로 받아 이미지 데이터를 생성하고 그 이미지를 해당 데이터를 바탕으로 채운다.
  • 성공 시, 해당 이미지에 대한 식별자를 반환하고 실패 시, NULL을 반환
int mlx_put_image_to_window(void *mlx_ptr, void *win_ptr, void *img_ptr, int x, int y);
  • 앞의 파라미터 3개는 위에서 생성한 mlx, 윈도우, 이미지의 식별자를 지정한다.
  • x, y 파라미터로 윈도우 안에서 이미지의 좌표를 지정한다.
char *mlx_get_data_addr(void *img_ptr, int bits_per_pixel, int *size_line, int *endian);
  • img_ptr 파라미터로는 사용할 이미지를 지정한다.
  • mlx_get_data_addr()는 이미지가 저장되어 있는 메모리의 시작 지점의 주소를 char * 형 포인터로 반환한다.
  • mlx_get_data_addr() 이 성공적으로 호출된다면 다음 세 개의 파라미터에 값이 지정됩니다.
  • bits_per_pixel(bpp) 파라미터에는 픽셀의 색상을 표현하는데 필요한 비트의 수가 입력된다.
  • size_line 파라미터에는 이미지 한 줄을 저장하는데 필요한 바이트 수가 입력된다.
  • endian 파라미터는 픽셀 색상의 저장 방식이 little endian(0 지정)인지 big endian(1 지정)인지를 나타낸다.
int	mlx_string_put(void *mlx_ptr, void *win_ptr, int x, int y, int color, char *string)
  • 문자열을 (x,y)에 지정한 색으로 출력해준다.
int	mlx_destroy_image(void *mlx_ptr, void *img_ptr)
  • 이미지 없애기

이미지 변환

적당한 png파일을 구해서 64 x 64 픽셀로 이미지를 조정한다.
이미지 변환 사이트 에서 png 파일을 xpm파일로 변환한다.


과제 진행

1. 구성요소 초기화

  • 인자가 잘못될 경우에 대한 에러처리
  • open 함수로 맵의 정보를 가져온다
  • init_param() 함수를 이용하여 game 구조체를 초기화
  • get_map() 함수를 이용하여 map의 구성요소를 입력한다.
    • 첫 번째 줄일 경우 ft_strlen() 함수를 사용하여 맵의 가로길이를 저장하고 check_map_line() 함수를 사용하여 get_next_line() 함수를 이용해 불러온 줄의 유효성을 검증한다. 첫 번째 줄이 아닐경우 바로 check_map_line() 함수를 사용하여 유효성을 검정한다.
      • set_map_value() 함수를 사용하여 game 구조체에 map의 구성요소를 입력한다.
        플레이어와 출구는 1개여야 하고, 콜렉터블은 개수를 저장한다.
    • check_map_components() 함수를 사용하여 맵의 요소에 대한 유효성을 검증한다.
      출구, 플레이어, 콜렉터블이 없다면 에러
  • init_mlxlibx() 함수를 사용하여 mlx를 위한 초기화를 진행한다.
    • mlx_init() 함수로 mlx를 초기화 한다.
    • mlx_new_window() 함수로 디스플레이에 새로운 윈도우를 띄운다.

2. 맵 입력, 유효성 검증

  • init_map() 함수를 사용하여 맵을 입력한다.
    • get_map_col() 함수를 사용하여 get_next_line() 으로 읽어온 한 줄을 맵에 입력한다. 여기서 마지막 줄이라면 무조건 벽만 존재해야 한다. 따라서 check_last_line() 함수를 사용하여 마지막 줄이 벽인지 체크한다.

3. 초기 맵 그리기

  • draw_map() 함수를 사용하여 맵을 그린다.
    • draw_pixels_of_tile() 함수를 사용하여 문자에 따라 이미지 정보를 저장하고, 해당 문자가 'P'인 경우 위치에 대한 이미지 정보도 저장한다.
      • mlx_xpm_file_to_image() 함수를 사용하여 xpm파일을 가져와 이미지로 변환한다.
    • mlx_put_image_to_window() 함수를 사용하여 해당 위치의 이미지 저장값을 출력한다.

4. 맵의 경로 체크

  • check_path() 함수를 사용하여 맵에 가능한 경로를 체크한다.
    • init_check() 함수를 사용하여 check 구조체를 초기화 한다.
      • init_visited() 함수를 사용하여 방문 여부를 판단하는 visited 배열을 생성한다.
    • dfs() 함수를 사용하여 재귀를 통해 맵에대한 완전 탐색을 진행하고 경로를 판단한다.
      탈출구를 찾지 못했거나, 콜렉터블을 모두 회수하지 못하는 경로가 존재할 경우 게임을 종료한다.
    • free_visited() 맵의 경로를 체크 했으므로 visited 배열을 비워준다.

5. 플레이어의 움직임 표현

  • mlx_hook(game.win_ptr, X_EVENT_KEYPRESS, 0, &press_key, &game) 을 사용하여 키가 눌릴때 마다 press_key라는 함수를 호출한다.
    • press_key() 함수에서 ESC가 눌렸을 경우 게임을 종료하고 아닐 경우 우선 check_player_move() 함수로 움직임을 체크한다.
    • check_player_move() 함수에서 'w', 'a', 's', 'd' 키에 대해서 움직임을 좌표에 입력한다. flag가 0이라면 아무런 이벤트가 일어나지 않는다.
    • check_valid_move() 함수에서 허용된 키가 아니거나, 벽인경우에는 0반환, 콜렉터블일 경우 회수한 콜렉터블을 1증가 후 빈공간으로 변환, 탈출구인 경우 모든 콜렉터블을 다 모았을 경우 게임 종료, 아닐 경우 콜렉터블이 더 남았다는 메시지를 출력한다. 그리고 빈 공간이거나, 콜렉터블이거나, 콜렉터블이 남았을 때 출구라면 1을 반환한다.
    • 올바른 움직임이라면 새로운 좌표를 플레이어 좌표로 저장한다.
    • draw_update_player() 함수로 플레이어에 대한 그림을 업데이트 해준다.
      • mlx_get_data_addr() 함수를 사용하여 이미지에 대한 정보를 받아와 빈공간으로 변환한 뒤 데이터에 맞게 빈공간으로 그린다. 그리고 플레이어에 대한 이미지를 업데이트 시켜주고 새롭게 움직인 위치에 대해 플레이어의 이미지를 업데이트 하고 움직임 횟수를 추가한다.
    • flag가 2라면 passing_exit() 함수를 호출하여 출구에 대한 이미지를 업데이트 시켜준다.
    • 움직임에 대한 정보를 print_move() 함수를 사용하여 터미널에 출력한다.

6. 게임 종료

  • mlx_hook(game.win_ptr, X_EVENT_EXIT, 0, &close_game, &game)를 사용하여 이벤트가 종료될 때 close_game 함수를 호출한다. game 구조체 내의 맵을 모두 비워주고 mlx_destroy_image() 함수를 사용하여 이미지를 없애고 프로그램을 종료한다.

코드 분석

so_long.h

#ifndef SO_LONG_H
# define SO_LONG_H
# include <fcntl.h>
# include <stdlib.h>
# include <unistd.h>
# include "./get_next_line/get_next_line.h"
# include "./mlx/mlx.h"

# define TILES 64
# define X_EVENT_KEYPRESS 2
# define X_EVENT_EXIT 17

# define KEY_W 13
# define KEY_A 0
# define KEY_S 1
# define KEY_D 2
# define KEY_ESC 53

typedef struct s_param // 이미지를 출력하기 위한 구조체
{
	int		x;
	int		y;
	int		tile_x;
	int		tile_y;
	void	*img_ptr;

}	t_param;

typedef struct s_map // 맵의 구성요소에 대한 구조체
{
	int	wall;
	int	player;
	int	collectible;
	int	exit;
}		t_map;

typedef struct s_img // 이미지에 대한 구조체
{
	void	*img_ptr;
	int		*data;
	int		bpp;
	int		size_line;
	int		endian;
}	t_img;

typedef struct s_game // 게임을 진행하는 데 필요한 구조체
{
	void		*mlx_ptr;
	void		*win_ptr;
	int			width;
	int			height;
	int			collected;
	int			move;
	t_param		position;
	t_img		img;
	t_map		map_textures;
	char		**map;
	int			valid_path;
}	t_game;

typedef struct s_check // 맵 유효성 검증을 위한 구조체
{
	int	y;
	int	x;
	int	collectible;
	int	**visited;
}	t_check;


void	ft_putchar_fd(char c, int fd);
void	ft_putstr_fd(char *s, int fd);
void	ft_putnbr_fd(int n, int fd);
char	*ft_itoa(int n);

int		check_valid_move(t_game *game, int x, int y, int keycode);
int		check_player_move(int keycode, t_game *game);
void	passing_exit(t_game *game, int prev_x, int prev_y);
void	draw_updated_player(t_game *game, int prev_x, int prev_y);
int		press_key(int keycode, t_game *game);

void	init_minilibx(t_game *game);
void	init_param(t_game *game);
void	get_map_col(t_game *game, char *line, int l);
void	init_map(t_game *game, int fd);
int		check_last_line(char *line);

void	set_map_value(t_game *game, char component);
void	check_map_line(t_game *game, char *line, int check_wall);
void	check_map_components(t_game *game);
void	get_map(t_game *game, int fd);

void	draw_pixels_of_tile(t_game *game, char texture);
void	draw_map(t_game *game, char *line, int l);

int		close_game(t_game *game, int type);
int		close_game_with_error(int type);
void	print_move(char c);

void	check_path(t_game *game);

#endif

utils.c

void	ft_putchar_fd(char c, int fd)
{
	write(fd, &c, 1);
}

void	ft_putstr_fd(char *s, int fd)
{
	int	i;

	i = 0;
	while (s[i])
		write(fd, &s[i++], 1);
}

void	rec_putnbr(int nb, int fd)
{
	char	c;

	if (nb == 0)
		return ;
	c = '0' + nb % 10;
	rec_putnbr(nb / 10, fd);
	ft_putchar_fd(c, fd);
}

void	ft_putnbr_fd(int n, int fd)
{
	char	c;

	if (n < 0)
	{
		write(fd, "-", 1);
		c = '0' - n % 10;
		rec_putnbr(-(n / 10), fd);
	}
	else
	{
		c = '0' + n % 10;
		rec_putnbr(n / 10, fd);
	}
	ft_putchar_fd(c, fd);
}

main.c

// 정상적으로 게임이 끝났을 때 프로그램 종료 함수
int	close_game(t_game *game, int type)
{
	int	i;

	i = 0;
	while (i < game->height)
	{
		free(game->map[i]);
		i++;
	}
	free(game->map);
	mlx_destroy_image(game->mlx_ptr, game->img.img_ptr);
	free(game->mlx_ptr);
	if (type)
	{
		ft_putstr_fd("------------------\n", 1);
		ft_putstr_fd(" The Game Is Over \n", 1);
		ft_putstr_fd("------------------\n", 1);
	}
	exit(0);
}

// 에러 메시지 출력 함수
int	close_game_with_error(int type)
{
	ft_putstr_fd("[Error]\n", 1);
	if (type == 0)
	{
		ft_putstr_fd("The map must be composed of only ", 1);
		ft_putstr_fd("5 possible characters(0, 1, C, E, P) !\n", 1);
	}
	else if (type == 1)
		ft_putstr_fd("There must be only one player !\n", 1);
	else if (type == 2)
		ft_putstr_fd("Theere must be only one exit !\n", 1);
	else if (type == 3)
		ft_putstr_fd("The map must be rectangular !\n", 1);
	else if (type == 4)
		ft_putstr_fd("The map must be closed or surrounded by walls !\n", 1);
	else if (type == 5)
	{
		ft_putstr_fd("Map must have at least one exit, one collectible, ", 1);
		ft_putstr_fd("and one starting position !\n", 1);
	}
	else if (type == 6)
		ft_putstr_fd("The map must have valid path !\n", 1);
	else if (type == -1)
		ft_putstr_fd("The files does not exist !", 1);
	exit(1);
}

// 몇 번 움직였는지 출력하는 함수
void	print_move(char c)
{
	ft_putstr_fd("Move : ", 1);
	ft_putnbr_fd(c, 1);
	ft_putchar_fd('\n', 1);
}

int	main(int argc, char *argv[])
{
	int		fd;
	t_game	game;

	if (argc != 2) // 인자가 잘못 됐을 때
	{
		ft_putstr_fd("[Error]\n", 1);
		ft_putstr_fd("Try './so_long [Map_name.ber]'\n", 1);
		return (0);
	}
	fd = open(argv[1], O_RDONLY); // 맵 열기
	if (fd == -1)
		close_game_with_error(-1);
	init_param(&game); // 구조체 초기화
	get_map(&game, fd); // 맵의 요소 입력
	init_minilibx(&game); // mlx를 위한 초기화
	close(fd);
	fd = open(argv[1], O_RDONLY); // 맵을 새로 읽어 오기 위해서 한번 더 open
	if (fd == -1)
		close_game_with_error(-1);
	init_map(&game, fd); // 맵 입력후 유효성 검증 및 초기 맵 그리기
	close(fd);
	mlx_hook(game.win_ptr, X_EVENT_KEYPRESS, 0, &press_key, &game); // 플레이어 움직임에 따라 맵 그리기
	mlx_hook(game.win_ptr, X_EVENT_EXIT, 0, &close_game, &game); // 게임 종료
	mlx_loop(game.mlx_ptr);
	return (0);
}

map.c

void	set_map_value(t_game *game, char component)
{
	if (component == '1' && !game->map_textures.wall) // '1'이고 map_textures에 wall이 없는 경우
		game->map_textures.wall = 1;
	else if (component == 'C') // 'C'인 경우 콜렉터블 1증가
		game->map_textures.collectible += 1;
	else if (component == 'P') // 'P'인 경우
	{
		if (game->map_textures.player > 0) // 플레이어가 존재한다면 에러
			close_game_with_error(1);
		game->map_textures.player = 1;
	}
	else if (component == 'E') // 'E' 인경우
	{
		if (game->map_textures.exit > 0) // 출구가 존재한다면 에러
			close_game_with_error(2);
		game->map_textures.exit = 1;
	}
}

void	check_map_line(t_game *game, char *line, int check_wall)
{
	int	i;
	int	len;

	i = -1;
	len = ft_strlen(line) - 1;
	if (len != game->width) // 읽어온 길이와 가로길이가 다를경우
		close_game_with_error(3);
	while (line[++i] && line[i] != '\n') // 개행문자 전까지
	{
		if ((i == 0 || i == len - 1) && line[i] != '1') // 양 끝이 벽이 아닐경우
			close_game_with_error(4);
		if (check_wall && line[i] != '1') // 첫 번째 줄이 벽이 아닌경우
			close_game_with_error(4);
		if (line[i] != '1' && line[i] != '0' && line[i] != 'P' && \
				line[i] != 'C' && line[i] != 'E')
		{
			close_game_with_error(0); // 다른 문자가 있는 경우
		}
		set_map_value(game, line[i]); // game 구조체에 맵의 구성요소 입력
	}
}

void	check_map_components(t_game *game)
{
	if (!game->map_textures.exit) // 출구가 없다면
		close_game_with_error(5);
	if (!game->map_textures.player) // 플레이어가 없다면
		close_game_with_error(5);
	if (!game->map_textures.collectible) // 콜렉터블이 없다면
		close_game_with_error(5);
}

void	get_map(t_game *game, int fd)
{
	char	*line;
	int		h;
	int		check_wall;

	h = 0;
	check_wall = 1;
	while (1)
	{
		// 파일을 한 줄 씩 읽어오기
		line = get_next_line(fd);
		if (!line)
			break ;
		if (h == 0) // 첫 번째 줄일경우
		{
			game->width = ft_strlen(line) - 1; // 가로길이 입력
			check_map_line(game, line, check_wall); // 해당 줄 유효성 검증, 모두 벽이어야함
		}
		else // 아닐 경우
			check_map_line(game, line, !check_wall); // 해당 줄 유효성 검증
		h += 1;
		free(line); // 다음을 위해 free
	}
	game->height = h; // 최종적으로 세로길이 입력
	check_map_components(game); // 맵의 요소에 대한 유효성 검증
}

init.c

// mlx를 init
void	init_minilibx(t_game *game)
{
	int	w;
	int	h;

	w = game->width; // 맵의 가로 입력
	h = game->height; // 맵의 세로 입력
	game->mlx_ptr = mlx_init(); // mlx 초기화
	game->win_ptr = mlx_new_window(game->mlx_ptr, w * TILES, \
	h * TILES, "[so_long]"); // mlx 윈도우 초기화, 이름은 so_long으로
}

// game 구조체를 초기화
void	init_param(t_game *game)
{
	game->width = 0;
	game->height = 0;
	game->map_textures.wall = 0;
	game->map_textures.player = 0;
	game->map_textures.collectible = 0;
	game->map_textures.exit = 0;
	game->collected = 0;
	game->move = 0;
	game->map = NULL;
	game->position.x = 0;
	game->position.y = 0;
	game->valid_path = 0;
}

void	get_map_col(t_game *game, char *line, int l)
{
	int		i;

	i = 0;
	game->map[l] = (char *)malloc(sizeof(char) * game->width); // 가로길이 만큼 동적 할당
	while (line[i])
	{
		if (line[i] == 'P') // 'P', 플레이어 라면
		{
			game->position.y = l;
			game->position.x = i;
			// game.position에 좌표 입력
		}
		game->map[l][i] = line[i]; // 맵 입력
		i++;
	}
}

// map의 마지막 줄을 체크
int	check_last_line(char *line)
{
	int	i;

	i = 0;
	while (line[i] && line[i] != '\n')
	{
		if (line[i] != '1') // 마지막 줄이 벽이 아니라면 에러
		{
			close_game_with_error(4);
			return (0);
		}
		i++;
	}
	return (1);
}

void	init_map(t_game *game, int fd)
{
	int		i;
	int		is_valid;
	char	*line;

	i = 0;
	is_valid = 1;
	game->map = (char **)malloc(sizeof(char *) * game->height); // 맵을 입력하기 위해 동적 세로길이만큼 동적 할당
	while (1)
	{
		line = get_next_line(fd);
		if (!line)
			break ;
		get_map_col(game, line, i); // 맵 입력
		if (i + 1 == game->height) // 마지막 줄이라면
			is_valid = check_last_line(line); // 마지막 줄 체크
		if (!is_valid) // 마지막 줄에 대한 타당성 검증
			close_game(game, 0);
		draw_map(game, line, i); // 맵 그리기
		free(line);
		i++;
	}
	check_path(game); // 맵에 가능한 경로 체크
	ft_putstr_fd("------------------\n", 1);
	ft_putstr_fd("   Game start !   \n", 1);
	ft_putstr_fd("------------------\n", 1);
}

path.c

void	free_visited(int **visited) // visited 배열 free
{
	int	i;

	i = -1;
	while (visited[++i])
	{
		free(visited[i]);
		visited[i] = 0;
	}
	free(visited);
	visited = 0;
}

int	**visited_init(t_game *game) // visited 배열 생성
{
	int	**visited;
	int	i;
	int	j;

	i = -1;
	visited = (int **)malloc(sizeof(int *) * game->height);
	if (!visited)
		return(0);
	while (++i < game->height - 1)
	{
		visited[i] = (int *)malloc(sizeof(int) * (game->width + 1));
		if (!visited[i])
		{
			free_visited(visited);
			return (0);
		}
		j = -1;
		while (j < game->width)
			visited[i][++j] = 0; // visited 배열을 0으로 초기화
	}
	return (visited);
}

void	dfs(t_game *game, t_check *check, int y, int x)
{
	const int	dy[4] = {1, -1, 0, 0};
	const int	dx[4] = {0, 0, 1, -1}; // 네 방향 탐색
	int			ny;
	int			nx;
	int			i;

	check->visited[y][x] = 1;
	if (game->map[y][x] == 'C')
		check->collectible -= 1;
	if (game->map[y][x] == 'E')
	{
		game->valid_path = 1; // 출구가 존재하므로 유효한 경로로 임시 지정
		return ;
	}
	i = -1;
	while (++i < 4)
	{
		ny = y + dy[i];
		nx = x + dx[i];
		if (game->map[ny][nx] != '1' && !check->visited[ny][nx]) // 새로운 좌표가 벽이 아니고 방문하지 않았을 떄 dfs 탐색
			dfs(game, check, ny, nx);
	}
}

void	init_check(t_game *game, t_check *check)
{
	check->visited = visited_init(game); // visited 배열 입력
	check->y = game->height; // 세로길이 입력
	check->x = game->width; // 가로길이 입력
	check->collectible = game->map_textures.collectible; // 콜렉터블의 개수 입력
}

void	check_path(t_game *game)
{
	t_check	check;

	init_check(game, &check); // check 구조체 초기화
	dfs(game, &check, game->position.y, game->position.x); // dfs 깊이우선탐색 실행 (완탐 한번 걸기)
	if (!game->valid_path || check.collectible > 0) // 탈출구를 못찾았거나 존재하는 콜렉터블을 모두 지우지 못했을 시 경로가 존재 x
		close_game_with_error(6);
	free_visited(check.visited); // visited 배열 free
}

draw.c

void	draw_pixels_of_tile(t_game *game, char texture)
{
	int	w;
	int	h;

	if (texture == '1') // '1'일 경우 벽
		game->img.img_ptr = mlx_xpm_file_to_image(game->mlx_ptr, \
				"imgs/wall.xpm", &w, &h);
	else if (texture == 'C') // 'C'일 경우 콜렉터블
		game->img.img_ptr = mlx_xpm_file_to_image(game->mlx_ptr, \
				"imgs/collectible.xpm", &w, &h);
	else if (texture == 'E') // 'E'일 경우 출구
		game->img.img_ptr = mlx_xpm_file_to_image(game->mlx_ptr, \
				"imgs/exit.xpm", &w, &h);
	else if (texture == '0') // '0'일 경우 빈공간
		game->img.img_ptr = mlx_xpm_file_to_image(game->mlx_ptr, \
				"imgs/empty.xpm", &w, &h);
	else if (texture == 'P') // 'P'인 경우 플레이어
	{
		game->img.img_ptr = mlx_xpm_file_to_image(game->mlx_ptr, \
				"imgs/player.xpm", &w, &h);
		game->position.img_ptr = game->img.img_ptr; // 플레이어 위치에 대한 이미지 포인터도 저장해줌
	}
}

void	draw_map(t_game *game, char *line, int l)
{
	int	i;

	i = 0;
	while (line[i])
	{
		if (line[i] == '0') // '0'일 경우에 빈 공간이므로 pass
		{
			i++;
			continue ;
		}
		if (line[i] == 'P') // 'P'일 경우 플레이어
		{
			game->position.tile_x = i * TILES; // 플레이어의 x좌표 * 64
			game->position.tile_y = l * TILES; // 플레이어의 y좌표 * 64 (64픽셀로 그림 그리기 때문) -> 가장 오른쪽 하단 꼭짓점이 좌표가 됨
		}
		draw_pixels_of_tile(game, line[i]);
		mlx_put_image_to_window(game->mlx_ptr, game->win_ptr, \
		game->img.img_ptr, i * TILES, l * TILES); // 해당 위치의 이미지 저장값을 출력
		i++;
	}
}

player.c

int	check_valid_move(t_game *game, int y, int x, int keycode)
{
	char	cur;

	cur = game->map[y][x]; // cur은 플레이여의 현재 위치
	if (keycode != KEY_W && keycode != KEY_S && \
	keycode != KEY_A && keycode != KEY_D)
		return (0); // 허용된 키가 아닌경우 0반환
	if (cur == '1')
		return (0); // 벽인 경우 0반환
	else if (cur == 'C') // 콜렉터블 인경우
	{
		game->collected += 1; // 콜렉터블 1증가
		game->map[y][x] = '0'; // 콜렉터블 위치 빈공간으로 변환
	}
	else if (cur == 'E') // 탈출구인 경우
	{
		if (game->collected == game->map_textures.collectible) // 전체 맵의 콜렉터블과 모은 콜렉터블이 같을경우
			close_game(game, 1); // 정상적으로 게임 종료
		else
			ft_putstr_fd("There's still a collectible left!\n", 1); // 아닐 경우 콜렉터블이 더 남았다는 메시지
	}
	return (1); // 빈공간이거나 콜렉터블이거나 콜렉터블이 남았을 때 출구라면 1 반환
}

int	check_player_move(int keycode, t_game *game)
{
	int	nx;
	int	ny;
	int	flag;

	flag = 1;
	ny = game->position.y; // 플레이어의 y좌표
	nx = game->position.x; // 플레이어의 x좌표
	if (keycode == KEY_W) // 'W'를 눌렀을 때 y 감소, 위로 올라가기
		ny--;
	else if (keycode == KEY_S) // 'S'를 눌렀을 때 y 증가, 아래로 내려가기
		ny++;
	else if (keycode == KEY_A) // 'A'를 눌렀을 때 x 감소, 왼쪽 방향
		nx--;
	else if (keycode == KEY_D) // 'D'를 눌렀을 때 x 증가, 오른쪽 방향
		nx++;
	else // 아닐경우 flag = 0
		flag = 0;
	flag = check_valid_move(game, ny, nx, keycode); // 빈공간이거나 콜렉터블이거나 콜렉터블이 남았을 때 출구라면 1 반환
	if (!flag) // 잘못된 경우라면 해당 플레그 반환
		return (flag);
	if (game->map[game->position.y][game->position.x] == 'E')
		flag = 2;
	game->position.y = ny; // 플레이어의 y좌표 업데이트
	game->position.x = nx; // 플레이어의 x좌표 업데이트
	return (flag); // 출구라면 2반환, 뭔가 남았는데 올바른 경우 1반환
}

void	passing_exit(t_game *game, int prev_y, int prev_x)
{
	draw_pixels_of_tile(game, 'E'); // 출구에 대한 이미지 업데이트
	mlx_put_image_to_window(game->mlx_ptr, game->win_ptr, \
			game->img.img_ptr, prev_x, prev_y);
}

void	draw_updated_player(t_game *game, int prev_y, int prev_x)
{
	int		h;
	int		w;

	game->img.data = (int *)mlx_get_data_addr(game->position.img_ptr, \
	&game->img.bpp, &game->img.size_line, &game->img.endian);
	h = 0;
	while (h < TILES) // 원래 있던 플레이어의 공간의 데이터를 빈 공간으로 교체
	{
		w = 0;
		while (w < TILES)
		{
			game->img.data[h * TILES + w] = 0;
			w++;
		}
		h++;
	}
	mlx_put_image_to_window(game->mlx_ptr, game->win_ptr, \
	game->position.img_ptr, prev_x, prev_y); // 데이터에 맞게 빈 공간으로 그리기
	draw_pixels_of_tile(game, 'P'); // 플레이어에 대한 이미지 업데이트
	mlx_put_image_to_window(game->mlx_ptr, game->win_ptr, \
	game->position.img_ptr, game->position.tile_x, game->position.tile_y); // 새롭게 움직인 위치에 대한 플레이어의 그림 업데이트
	game->move += 1; // 움직인 횟수 증가
}

int	press_key(int keycode, t_game *game)
{
	int	prev_x;
	int	prev_y;
	int	flag;

	if (keycode == KEY_ESC) //'ESC'를 눌렀을 때 게임 종료
		close_game(game, 1);
	flag = check_player_move(keycode, game); // 끝나면 2반환, 뭔가 남았는데 올바른 경우 1반환
	if (flag)
	{
		prev_y = game->position.tile_y; // 플레이어의 이전 픽셀 y좌표를 가져옴
		prev_x = game->position.tile_x; // 플레이어의 이전 픽셀 x좌표를 가져옴
		if (keycode == KEY_W) // W를 눌렀을 떄
			game->position.tile_y -= TILES; // y--이므로 64만큼 감소
		else if (keycode == KEY_S) // S를 눌렀을 때
			game->position.tile_y += TILES; // y++이므로 64만큼 증가
		else if (keycode == KEY_A) // A를 눌렀을 때
			game->position.tile_x -= TILES; // x--이므로 64만큼 감소
		else if (keycode == KEY_D) // D를 눌렀을 때
			game->position.tile_x += TILES; // x++이므로 64만큼 증가
		draw_updated_player(game, prev_y, prev_x); // 플레이어의 그림 업데이트
		if (flag == 2) // 플레그가 2일경우 출구로 이동
			passing_exit(game, prev_y, prev_x);
		print_move(game->move); // 움직임 업데이트
	}
	return (0);
}
profile
진짜 공부하자

0개의 댓글