miniLibX를 활용하여 맵을 구성하고, 직접 조작이 가능한 캐릭터로 수집품을 획득하여 지정된 탈출구로 탈출하는 구조의 미니게임을 만들어보는 프로젝트
만약 과제를 이제 시작한다면 MLX를 활용하는 예제들을 미리 해보는것을 추천
(https://velog.io/@jen133/miniLibX)
int main(void)
{
t_param par;
int mapstat;
set_param(&par);
if (par.fd <= 0)
{
printf("Error\nInvalidMapFile\n");
return (0);
}
copymap(&par);
mapstat = check_map(&par);
if (mapstat == 0)
{
printf("Error\n");
freemap(&par);
exit (0);
return (0);
}
set_p(&par);
par.win = mlx_new_window(par.mlx, par.win_width, par.win_height, "so_long");
drawmap(&par);
mlx_key_hook(par.win, &key_press, &par);
mlx_hook(par.win, PRESS_RED_BUTTON, 0, &redbut, &par);
mlx_loop(par.mlx);
return (0);
}
- 게임 실행에 필요한 각종 값들의 초기설정을 해줌 (set_param(t_param))
- map.ber 파일에 저장되어있는 맵정보를 한줄씩 불러읽어 연결리스트에 저장해줌 (copymap(t_param))
- 맵정보가 맵 구현규칙에 맞는지 검증해줌 (check_map(t_param))
- 맵정보가 맵 구현 규칙에 맞지않다면 오류메세지를 띄우고 할당한 메모리를 free 해준 뒤 종료함
- 맵정보에 문제가 없다면 플레이를 위해 초기 플레이어의 시작위치를 저장해줌 (set_p(t_param))
- 게임화면을 띄우기위해 mlx를 사용하여 적정크기의 새 창을 띄워줌 (mlx_new_window())
- 저장해둔 맵 정보를 기반으로 윈도우에 이미지를 그려줌 (drawmap(t_param))
- 키 입력을 받고 받은 키에따른 동작(key_press())을 할 수있도록 키입력 이벤트를 수신하도록 함 (mlx_key_hook())
- 좌측 상단의 프로그램 종료버튼이 눌리는것(redbut())을 수신하고 동작할 수 있도록 함(mlx_hook())
- 프로그램이 종료되지않고 지속적으로 이벤트를 받을 수 있게 무한 루프를 돌도록 함(mlx_loop())
void set_param(t_param *par) { par->fd = open("maps/map.ber", O_RDONLY); if (par->fd <= 0) return ; par->map = NULL; // map을 불러와서 저장할 공간 par->count_e = 0; // 탈출구의 갯수 par->count_p = 0; // 시작지점의 갯수 par->count_c = 0; // 수집품의 갯수 par->win_width = 0; // 윈도우의 너비 par->win_height = 0; // 윈도우의 높이 par->x = 0; // 이미지의 x축 크기 par->y = 0; // 이미지의 y축 크기 par->move = 0; // 이동 횟수 par->p_x = 0; // 플레이어의 x좌표 par->p_y = 0; // 플레이어의 y좌표 par->mlx = mlx_init(); // mlx par->g = mlx_xpm_file_to_image(par->mlx, "imgs/g.xpm", &par->wi, &par->he);// 땅 이미지 par->w = mlx_xpm_file_to_image(par->mlx, "imgs/r.xpm", &par->wi, &par->he);// 벽 이미지 par->c = mlx_xpm_file_to_image(par->mlx, "imgs/c.xpm", &par->wi, &par->he);// 수집품 이미지 par->e = mlx_xpm_file_to_image(par->mlx, "imgs/e.xpm", &par->wi, &par->he);// 탈출구 이미지 par->p = mlx_xpm_file_to_image(par->mlx, "imgs/p.xpm", &par->wi, &par->he);// 플레이어 이미지 par->win = NULL;// 윈도우 }
필요한 값들을 저장해둔 구조체의 여러 값들을 초기화 하고, 이미지파일을 세팅해줌(mlx_xpm_file_to_image()) 또한 사용할 mlx도 초기화 해줌
int copymap(t_param *par) { char *str; str = get_next_line(par->fd); //get_next_line 사용해 맵파일을 한줄씩 읽어줌 while (str != NULL) //읽어온 문자열이 NULL일때까지 (다 읽을때 까지) { copyline(str, par); //해당 줄을 복사하고 연결리스트에 넣어줌 free(str); // 읽어온 문자열의 메모리할당 해제 str = get_next_line(par->fd); // 다음 문자열을 읽어옴 } free (str); // 읽어온 문자열의 메모리 할당 해제 return (1); } int copyline(char *str, t_param *par) { t_mapline *line; t_mapline *currline; currline = par->map; // 문자열을 저장할 연결리스트의 시작을 가져옴 line = malloc(sizeof(t_mapline)); //새 문자열을 저장할 저장공간 할당 if (!line) // 할당 실패시 { freemap(par); //여태 할당한 저장공간을 모두 해제 return (0); } line->next = NULL; // 초기값 설정 line->line = ft_strdup(str); //읽어온 문자열을 새로운 저장공간을 할당해서 넣어줌 if (par->map == NULL) // 연결리스트의 첫 부분이 NULL이라면 par->map = line; // 시작의 line에 바로 넣어줌 else { while (currline->next != NULL) // 연결리스트의 끝까지 이동 currline = currline->next; currline->next = line; // 문자열을 넣어줌 } return (1); } void freemap(t_param *par) // 여태 할당한 저장공간을 모두 해제해줌 { t_mapline *currline; t_mapline *nextline; currline = par->map; while (currline) // 연결리스트의 끝까지 순회 { nextline = currline->next; // 할당 해제 전 다음 연결리스트를 미리 가져옴 free (currline->line); // 내부에 들어있는 line부터 해제 free (currline); // 현재 공간을 해제 currline = nextline; // 다음 연결리스트로 이동 } free (currline); // 마지막에 보고있던 저장공간 해제 }
int check_map(t_param *par) { int map_line; //맵이 총 몇라인으로 이뤄져있는지 저장 map_line = check_line_len(par); // 맵 줄수를 확인하고 저장 par->win_height = map_line * par->he; // 윈도우 높이를 계산하고 저장 par->win_width = (ft_strlen(par->map->line) - 1) * par->wi; // 윈도우 너비를 계산하고 저장 if (map_line) // 줄수가 맞는지 확인 if (check_wall(par, map_line)) // 벽이 온전한지 확인 if (check_map_char(par)) // 구성요소가 조건에 맞는지 확인 return (1); // 맞다면 1 반환 return (0); // 하나의 조건이라도 맞지않다면 0 반환 } int check_line_len(t_param *par) { t_mapline *curline; int before_line_len; int curr_line_len; int line_count; before_line_len = 0; line_count = 0; curline = par->map; before_line_len = ft_strlen(curline->line); // 첫줄의 글자 수를 확인 while (curline) // 마지막 라인까지 반복 { line_count++; // 줄수 + 1 curr_line_len = ft_strlen(curline->line); // 현재 보고있는 라인의 글자수 확인 if (before_line_len != curr_line_len) // 이전 글자수와 다르다면 return (print_err("줄 길이가 다름")); // 줄길이가 다르기때문에 에러반환 before_line_len = curr_line_len; // 글자수가 같다면 현재 글자수를 이전 글자수로 변경 curline = curline->next; // 다음줄 확인 } if (line_count >= 3) // 라인이 3줄 이하라면 return (line_count); // 구성요소가 다 들어갈 수 없으므로 에러 반환 return (print_err("줄이 3줄 이하임")); } int check_wall(t_param *par, int linenum) // 벽을 확인 { int line_count; int end_line; t_mapline *curline; line_count = 0; end_line = linenum; curline = par->map; while (curline) // 마지막 라인까지 반복 { if (line_count == 0 || line_count == end_line - 1) // 첫줄 또는 마지막줄 일때 { if (!check_wall_end(curline->line)) // 전부 1이 아니면 에러 반환 return (print_err("벽이 다 둘러져있지않습니다")); } else { if (!check_wall_middle(curline->line)) // 시작과 끝이 1이 아니라면 에러 반환 return (print_err("벽이 다 둘러져있지않습니다")); } line_count++; curline = curline->next; } return (1); } int check_map_char(t_param *par) // 맵의 구성요소를 확인 { t_mapline *curline; char *str; curline = par->map; while (curline) { str = curline->line; while (*str != '\n') // 라인의 끝까지 반환 { if (*str == 'E') // E가 있다면 e갯수 증가 par->count_e++; else if (*str == 'P') // P가 있다면 p갯수 증가 par->count_p++; else if (*str == 'C') // C가 있다면 c갯수 증가 par->count_c++; else if (*str != '1' && *str != '0') // 1 또는 0이 아니라면 에러 return (0); str++; } curline = curline->next; } if (par->count_e != 1 || par->count_p != 1 || par->count_c < 1) // 각 구성요소의 갯수가 1이 아닐시 에러 return (print_err("탈출구, 시작지점, 수집품의 시작조건이 맞지않습니다")); return (1); } int check_wall_end(char *str) // 시작과 끝 벽 확인 { while (*str != '\n') { if (*str != '1') // 1이아닌 다른게 있다면 에러 return (print_err("끝에 1아닌게 있음")); str++; } return (1); } int check_wall_middle(char *str) { char a; if (*str != '1') // 처음이 1이 아니라면 에러 return (print_err("중간 시작이 1 아님")); while (*str != '\n') // 끝까지 이동 { a = *str; str++; } if (a != '1') // 끝이 1이 아니라면 에러 return (print_err("중간 끝이 1 아님")); return (1); } int print_err(char *str) // 에러메시지를 출력해줌 { printf("%s\n", str); return (0); }
void set_p(t_param *par) // 플레이어의 현재 좌표를 찾는 함수 { t_mapline *curline; char *str; curline = par->map; par->p_x = 0; par->p_y = 0; while (curline) //맵 문자열을 순회 { str = curline->line; while (*str != '\n') // 문자열의 끝까지 순회 { if (*str == 'P') // 플레이어의 위치인 P가 나올때까지 return ; str++; par->p_x++; // x좌표값을 증가시킴 } curline = curline->next; par->p_x = 0; // 새로운 문자열로 가야하니 x좌표값 초기화 par->p_y++; // 문자열을 바꿀때마다 y좌표값 증가 } }
int drawmap(t_param *par) { t_mapline *currline; char *str; int x; int y; x = 0; y = 0; mlx_clear_window(par->mlx, par->win); // 윈도우에 그려진 이미지들을 모두 지워줌 currline = par->map; while (currline) // 맵정보를 순회 { str = currline->line; while (*str != '\n') // 문자열을 순회 { draw_img(*str, par, x, y); // 현재 문자에 해당하는 이미지를 그려줌 str++; x += par->wi; // 이미지가 그려질 x좌표값을 이미지의 너비만큼 증가시켜줌 } currline = currline->next; // 다음 문자열로 x = 0; y += par->he; // 이미지가 그려질 y좌표값을 이미지의 높이만큼 증가시켜줌 } return (0); } void draw_img(char a, t_param *par, int x, int y) { if (a == '0') // 문자가 0이라면 mlx_put_image_to_window(\ par->mlx, par->win, par->g, x, y); //땅 이미지를 그려줌 else if (a == '1') // 문자가 1이라면 { mlx_put_image_to_window(\ par->mlx, par->win, par->g, x, y); //땅을 먼저 그리고 mlx_put_image_to_window(\ par->mlx, par->win, par->w, x, y); //그 위에 벽을 그려줌 } else if (a == 'P' || a == 'C' || a == 'E') // P,C,E중 하나라면 { mlx_put_image_to_window(\ par->mlx, par->win, par->g, x, y); //땅을 우선 그려주고 if (a == 'P') mlx_put_image_to_window(\ par->mlx, par->win, par->p, x, y); // P라면 캐릭터를 그려줌 else if (a == 'C') mlx_put_image_to_window(\ par->mlx, par->win, par->c, x, y); // C라면 수집품을 그려줌 else if (a == 'E') mlx_put_image_to_window(\ par->mlx, par->win, par->e, x, y); // E라면 탈출구를 그려줌 } }
int key_press(int keycode, t_param *par) { if (keycode == KEY_W) { move_up(par, par->p_y); // W를 누르면 up 호출 } else if (keycode == KEY_S) { move_down(par, par->p_y); //S누르면 down 호출 } else if (keycode == KEY_A) { move_left(par, par->p_y); //A누르면 left호출 } else if (keycode == KEY_D) { move_right(par, par->p_y); //D누르면 right 호출 } else if (keycode == KEY_ESC) esc_press(keycode, par); //esc 누르면 종료 호출 printf("step: %d\n", par->move); //움직인 횟수 출력 drawmap(par); //맵을 다시 그려줌 return (0); } void move_left(t_param *par, int y) // 왼쪽으로 이동 { t_mapline *curline; char *str; curline = par->map; while (y-- != 0) curline = curline->next; //플레이어의 y좌표에 해당하는 문자열로 이동 str = curline->line; if (str[par->p_x - 1] == '1') return ; // 만약 이동할 위치가 1이라면 벽이므로 그냥 종료 else if (str[par->p_x - 1] == 'E') if (get_e(par)) return ; // 이동할 위치가 탈출구이고 조건이 맞다면 게임종료, 안맞다면 함수를 종료 if (str[par->p_x - 1] == 'C') par->count_c--; // 이동할 위치가 수집품이라면 수집품 카운트를 줄여줌 str[par->p_x - 1] = 'P'; // 이동할 위치를 P로 바꿔주고 str[par->p_x] = '0'; // 원래 위치를 0으로 바꿔줌 par->move++; // 이동횟수 1 증가 set_p(par); // 플레이어의 좌표값을 수정해줌 } void move_right(t_param *par, int y) // 오른쪽으로 이동 { // (left와 동일하게 작동함) t_mapline *curline; char *str; curline = par->map; while (y-- != 0) curline = curline->next; str = curline->line; if (str[par->p_x + 1] == '1') return ; else if (str[par->p_x + 1] == 'E') if (get_e(par)) return ; if (str[par->p_x + 1] == 'C') par->count_c--; str[par->p_x + 1] = 'P'; str[par->p_x] = '0'; par->move++; set_p(par); } void move_up(t_param *par, int y) // 위로 이동하는 함수 { t_mapline *curline; char *str; curline = par->map; str = curline->line; while (y != 0) { if (y == 1) // y가 1일때 즉 이동할 y좌표를 보고있을 때 { if (str[par->p_x] == '1') return ; // 갈 위치가 1이라면 함수 종료 else if (str[par->p_x] == 'E') if (get_e(par)) return ; // 갈 위치가 E이고 탈출조건이 맞으면 게임종료 아니면 함수 종료 if (str[par->p_x] == 'C') par->count_c--; // 갈 위치가 C라면 수집품 카운트 -1 str[par->p_x] = 'P'; // 갈 위치를 P로 변경해줌 } curline = curline->next; // 다음 문자열로 (한줄 아래) str = curline->line; y--; // 반복마다 y값 감소 } str[par->p_x] = '0'; // 현재문자열의 x좌표위치를 0으로 바꿈 par->move++; // 이동횟수 1 증가 set_p(par); // 플레이어 좌표 수정 } void move_down(t_param *par, int y) // 아래로 이동하는 함수 { t_mapline *curline; char *str; char *savestr; curline = par->map; while (y != 0) // 현재 플레이어가 있는 y좌표까지 문자열 이동 { curline = curline->next; y--; } savestr = curline->line; // 현재y좌표 문자열을 저장해둠 curline = curline->next; // 다음 문자열로 str = curline->line; if (str[par->p_x] == '1') return ; // 갈곳이 1이라면 함수 종료 else if (str[par->p_x] == 'E') if (get_e(par)) return ; // 이동할 위치가 탈출구이고 조건이 맞다면 게임종료, 안맞다면 함수를 종료 if (str[par->p_x] == 'C') par->count_c--; // 이동할 위치가 수집품이라면 수집품 카운트를 줄여줌 str[par->p_x] = 'P'; //이동할 위치를 P로 바꿔주고 savestr[par->p_x] = '0'; // 아까 저장해둔 현재문자열의 현재위치를 0으로 변경 par->move++; // 이동횟수 증가 set_p(par); // 플레이어 좌표 수정 }
안녕하세요, par->g = mlx_xpm_file_to_image(par->mlx, "imgs/g.xpm", &par->wi, &par->he); 이부분이 나중에 어떻게 쓰이는지 잘 이해가 안되는데 설명 부탁드려도 될까요? 제가 궁금한 부분은 &par->wi, &par->he의 wi나 he이 값이 다른 그림의 값으로 덮어씌워지지 않는가거든요.