본문 바로가기
42seoul

So Long : miniLibX로 만드는 간단한 2D 게임

by objet 2022. 4. 2.
프로젝트 개요

Mandatory Part

So Long은 그래픽 디자인 프로젝트라고 볼 수 있다.

주인공이 특정 수집품을 모두 모은 뒤 맵을 탈출하는 작은 2D게임을 만들어야 한다.

반드시 miniLibX를 사용해야 하며, 운영체제에서 이용 가능한 라이브러리와 과제에서 제공되는 소스 중 하나를 사용해야 한다.

작업창 관리(창 최소화, 다른 창으로 전환 등의 동작)는 버벅거림 없이 동작해야 한다.

지도의 세 가지 요소들: 벽, 수집품, 그리고 빈 공간

플레이어의 목표는 모든 수집품을 모으고 최소한의 움직임으로 맵을 탈출하는 것.

각 움직임마다 현재까지 움직인 횟수가 쉘에 출력되어야 한다.

플레이어는 벽을 뚫고 지나갈 수 없으며, 게임은 2D 시점으로 제작하여야 한다. (탑뷰 또는 프로필)

W, A, S, D 키를 이용하여 주인공을 상하좌우로 조작합니다.

ESC와 창 좌상단의 빨간 버튼 (mac)을 누르면 창을 닫고 게임을 정상적으로 종료한다.

프로그램은 .ber 확장자의 파일을 첫 번째 인자로 받아야 한다.

지도는 단 5개의 가능한 문자열로만 구성되어야 한다: 0은 빈 공간, 1은 벽, C는 수집품, E는 맵의 출구, P는 주인공의 시작지점

./so_long map/map1.ber    또는     ./so_long_bonus map/map1.ber   로 실행
 

올바른 map의 요건

지도는 벽으로 둘러쌓여 있어야 하며, 그렇지 않으면 에러를 반환해야 한다.

지도는 최소한 하나의 출구, 하나의 물고기 (수집품), 하나의 시작 지점을 포함해야 한다.

지도는 반드시 직사각형 모양이어야 한다.

지도 파일에서 어떠한 허점이 발견된다면, 프로그램은 "Error\n" 과 본인이 직접 정한 에러 메시지를 출력한 후 제대로 종료되어야 한다.

 

Bonus Part

적 추가. 주인공이 적에게 닿으면 게임에서 패배합니다.

스프라이트에 움직임을 주는 건 어떨까요?

쉘 대신, 화면상에 현재까지 움직인 횟수를 출력할 수도 있습니다.

 

게임에 쓰일 그래픽소스는 자체제작함.

좋아하는 게임을 모티브로 하여 도트를 찍음.

만약 그래픽소스를 직접 만들고 싶다면 

도트패턴 참고할 사이트 하나 추천해드립니다

https://kandipatterns.com

 

크기는 32*32 pixel로 고정하였고

벽, 바닥, 캐릭터, 출구, 콜렉션아이템, 적 의 그래픽소스가 필요합니다.

스프라이트 애니메이션 효과를 넣고싶다면 코드에 delay를 줘서 3~4마이크로초 간격으로 xpm파일을 바꾸는 방법 추천!

 


 

MiniLibX의 유용한 함수들

miniLibX란?

miniLibX란 42서울에서 학생들의 학습을 위하여 자체제작한 그래픽 라이브러리이다.

주로 mlx라 줄여서 부르며 이하 서술에도 이 호칭을 사용한다.

mlx는 openGL버전과 mms버전이 있고 나는 opengl버전을 사용하였다.

이전에 openGL을 다뤄본 적이 있어 이해가 쉬울 것 같았기 때문이다.

 

아래는 so_long 과제에서 필요했고, 사용했던 함수들을 나열하며 정리한 것이다.

※ 아래 나열한 순서는 작성자가 짠 코드의 설명을 용이하게 하기 위한 순서이기 때문에, 

용도별로 나눠놓지 않아 이해하는 데 귀찮을 수 있음.

 

mlx_init

#include <mlx.h>
void *mlx_init();
 

mlx의 모든 함수를 사용하기 전에 사용하여 mlx함수들이 올바르게 동작할 수 있도록 초기화 시켜주는 함수이다.

소프트웨어와 디스플레이를 연결하는 기능이 있으며,

반환 값은 연결에 성공할 시 연결 식별자인 non-null포인터, 연결에 실패할 시 null포인터를 반환한다.

 

 

mlx_new_window

#include <mlx.h>
void *mlx_new_window (void *mlx_ptr, int size_x, int size_y, char *title);
 

이 함수는 새로운 창을 title이라는 이름으로 생성하는 함수이다.

miniLibX는 이와 같은 함수를 이용하여 여러 창을 제어할 수 있다.

첫번째 인자인 mlx_ptr는 mlx_init을 설명할 때 나왔던 연결 식별자이며, 디스플레이와의 연결 성공 여부를 판단한다.

다음 인자들인 size_x, size_y는 각각 새 창의 x, y 사이즈이다.

반환값은 창 생성에 성공했을 경우 non-null포인터 (init함수의 mlx_ptr 같이 void *win_ptr에 주로 넣어 윈도우를 구별한다),

생성에 실패했을 경우 null 포인터를 반환한다.

 

 

mlx_xpm_file_to_image

#include <mlx.h>
void *mlx_xpm_file_to_image(void *mlx_ptr, char *filename, int *width, int *height);
 

.xpm 타입의 파일로부터 이미지를 생성하는 함수이다.

모든 타입의 xpm과 png 이미지를 못 읽을 수도 있으나, 투명도는 제어가 가능하다.

이미지 생성에 성공할 시 img_ptr(이미지 식별자)를 반환한다. 실패할 경우 null포인터를 반환한다.

 

 

mlx_clear_window

#include <mlx.h>
int mlx_clear_window(void *mlx_ptr, void *win_ptr);
 

인자로 받는 window를 검은색으로 초기화하는 함수이다.

window위에 본격적으로 이미지를 올리기 위한 전처리 과정이다.

함수 프로토타입의 반환값은 int라고 명시되어있지만, 42서울의 mlx 매뉴얼에서는 아무것도 반환하지 않는다고 한다.

 

mlx_put_image_to_window

#include <mlx.h>
int mlx_put_image_to_window(void *mlx_ptr, void *win_ptr, void *img_ptr, int x, int y);
 

img_ptr로 받는 이미지를 win_ptr의 window로 출력하는 함수이다.

마지막의 int x, int y인자는 이미지가 위치할 window 내의 x, y좌표를 뜻한다.

 

 

mlx_key_hook

#include <mlx.h>
int mlx_key_hook(void *win_ptr, int (*funct_ptr)(), void *param);
 

키보드를 누르는 event가 발생했을 때 두번째 인자인 funct_ptr이 가리키는 callback function을 호출한다.

마지막 인자인 param은 callback function이 호출되었을 때 callback function의 인자로 사용된다.

 

 

mlx_string_put

#include <mlx.h>
int mlx_string_put(void *mlx_ptr, void *win_ptr, int x, int y, int color, char *string);
 

특정 윈도우에 문자열을 디스플레이하는 함수이다.

int x, int y 인자는 각각 문자열을 표시할 윈도우 상의 x, y 좌표를 의미한다.

string 배열에 담겨있는 문자열을 윈도우에 표시하며, 글자 색깔은 color로 입힌다.

이 함수 또한 단순하게 0을 반환한다.

추천 color 값 : 0x0066FF33

 

 

mlx_hook

#include <mlx.h>
int mlx_hook(void *win_ptr, int x_event, int x_mask, int (*funct)(), void *param);
 

mlx 상에 정의되어있는 모든 이벤트가 일어났을 때 hook을 하는 함수이다.

x_event와 x_mask는 어떤 이벤트가 일어났는지 mlx_hook에게 알려주는 인자들이다.

이벤트가 발생하면 funct_ptr이 가리키는 callback function을 호출한다. param은 callback function의 인자로 사용된다.

이 친구도 단순하게 0을 반환한다.

MacOS에서는 x_mask를 신경쓸 필요 없다. 리눅스만 필요한듯

x_event의 코드에 따라 어떤 이벤트가 오는지 알 수 있는데,

해당 코드 값이 17이면 red button을 누른 이벤트가 발생했음을 알 수 있다.

 

mlx_loop_hook

#include <mlx.h>
int mlx_loop_hook(void *mlx_ptr, int (*funct_ptr)(), void *param);
 

등록된 이벤트가 발생하지 않을 경우 funct_ptr이 가리키는 callback function을 호출한다.

이벤트가 발생하지 않았다고 종료되는 것이 아니라 계속해서 반복적으로 호출되는 함수이다.

자세히보면 두번째 인자가 int (*funct_ptr)()형태인데, 이것은 앞 포스팅의 minitalk에서 자주 보았던 형식이다.

즉, 이벤트가 계속해서 발생하지 않을 경우 callback되는 함수는

void *param의 인자를 가지고 int형의 반환 타입을 가지는 함수가 와야함을 알 수 있다.

 

 

mlx_loop

#include <mlx.h>
int mlx_loop(void *mlx_ptr);
 

mlx_loop는 이름처럼 무한루프를 도는 함수이다.

무한루프를 돌면서 이벤트가 발생할 경우 이벤트와 연결된 함수가 호출에 성공하게끔 하는 데에 의의가 있다.

반환값은 없는 함수이다.


So Long의 흐름

so_long.c

메인함수에서 t_game 구조체 배열 선언 후 동적할당, init호출, 에러별로 다른 에러메세지 출력.

이벤트 검출하는 함수 선언~

 

initever.c

게임 요소 전부 초기화하는 init함수 들어있음.

 

chmap.c 

실행파일의 두번째 인자로 오는 맵 파일(*.ber)을 읽고 검사하는 역할. check_map함수부터 시작.

char *read_map(fd, 문자열 저장할 장소) : 이름만 다를 뿐 Get Next Line이라 보면 됨.

 

map_check.c

1, 0, P, E, C 잘 들어있는지 검사.

오브젝트들이 유효한지 검사함. objinc에서 심볼들이 있는지 플래그 변수로 표시하고 P(player)의 수를 셈.

이후 1, 0, C, E가 최소 하나씩 있고 P는 하나 있는지 if 문으로 처리.

 

draw() 함수는 맵이 변경될 때마다(플레이어가 움직일 때) 다시 그려주기 위한 함수이다.

 

enemy.c

enemy의 움직임, 그리고 좌우로 한칸씩 이동하도록 하는 파일.

게임이 아직 안끝났을 때, 그리고 counter가 31의 배수일 때(애니메이션 실행 끝), ft_eventsw를 호출해서 이동하려는 방향에 뭐가 있는지 확인하고 조건에 맞으면 movement 방향으로 이동한다.

movement가 음수일 때 양수, 양수일 떄 음수로 바꿔주는 함수를 사용해 왔다리갔다리 애니메이션 구현~

 

이외에 여러 코드파일들 ...

코드를 중구난방으로 짠 감이 있어 헷갈리지 않고자 정리함