42SEOUL/Circle2

[42SEOUL] so_long

2023. 2. 18. 13:07
목차
  1. Chapter 1
  2. Foreword
  3. Chapter 2
  4. Goals
  5. Chapter 3
  6. Common Instructions
  7. Chapter 4
  8. Mandatory part
  9. Chapter 5
  10. Bonus part
  11. 느낀 점
728x90

Chapter 1

Foreword

우리는 간단한 2D게임을 mlx라이브러리를 활용하여 만들어야 한다.

2D게임을 만들기 위해선, 맵 타일, 타일셋, 스프라이트, 스프라이트 시트 등이 필요하다.

이곳에 그런게 있으니 참고하면 좋다. 다른 픽셀아트 사이트도 둘러보길 바란다.

Chapter 2

Goals

so_long을 통해 그래픽 디자인 프로젝트를 해볼 수 있고, 창 띄우기, 색상, 이벤트 설정하기, 모양 채우기 등과 같은 분야에서 능력이 향상 될 것이다.

Chapter 3

Common Instructions

mlx 라이브러리에 대해 알아야한다.

so_long에서 주로 사용하는 mlx라이브러리 함수는 다음과 같다.

  • mlx_init
  • mlx_new_window
  • mlx_xpm_file_to_image
  • mlx_put_image_to_window
  • mlx_hook
  • mlx_loop
  • mlx_string_put
void * mlx_init ();

#include <mlx.h>

void *mlx_ptr;

mlx_ptr = mlx_init();

mlx를 초기화 한다. mlx라이브러리에 관한 모든 것을 이용하기 전 실행해줘야한다.

void    *mlx_new_window(void *mlx_ptr, int size_x, int size_y, char *title);

void  *win_ptr;
win_ptr = mlx_new_window(mlx_ptr, 500, 500, "so_long");

window를 열어준다. 너비와 높이, window의 이름을 설정할 수 있다. mlx_init()으로 반환받은 mlx 포인터를 입력해주어야한다.

창 생성 실패 시 null반환.

void    *mlx_xpm_file_to_image(void *mlx_ptr, char *filename,
                   int *width, int *height);

void *img_ptr;
img_ptr = mlx_xpm_file_to_image(mlx_ptr, "char_path", width, height);

so_long 프로젝트를 위해 귀여운 이미지를 가져왔다면, 이미지 파일을 포인터에 저장해주어야 한다. xpm 파일을 써 주는게 png를 쓰는것보다 좋다. (png파일을 쓰면 에러가 날 수 있다고 함.)

int    mlx_put_image_to_window(void *mlx_ptr, void *win_ptr, void *img_ptr,
                int x, int y);

mlx_put_image_to_window(mlx_ptr, win_ptr, img_ptr, 0, 0);

저장한 이미지 포인터를 창에 직접 띄워준다. 들어가는 인자가 직관적이라 설명은 패스.

/* XPM */
static char *result[] = {
/* columns rows colors chars-per-pixel */
"32 32 25 1 ",
"  c #2E1E22",
". c gray16",
"X c gray22",
"o c #3A3A3A",
"O c #473D3C",
"+ c #534721",
"@ c #444444",
"# c #505050",
"$ c #685958",
"% c gray38",
"& c gray49",
"* c #964F00",
"= c #9D704C",
"- c #877A77",
"; c #A78D0E",
": c #D7906E",
"> c #FFD644",
", c #928A8C",
"< c #B0A39F",
"1 c #EEBE93",
"2 c #FFE1AF",
"3 c gray85",
"4 c #D9D9DD",
"5 c white",
"6 c None",
/* pixels */
"66666666666666666666666666666666",
"6666666666666OOOOOOO666666666666",
"66666666666OO$$$$$$$OO6666666666",
"666666666OO$-<-----<-$OO66666666",
"66666666O$--<-------<--$O6666666",
"6666666O$--<<<-----<<<--$O666666",
"666666O$----<<<<<<<<<----$O66666",
"666666O--$-------------$--O66666",
"66666O$-$----$--$--$----$-$O6666",
"66666O$$--$--$-$2$-$--$--$$O6666",
"66666O$$--$-$2$222$2$-$--$$O6666",
"66666O$$$-$$222222222$$-$$$O6666",
"66666O$:$$1222222222221$$:$O6666",
"666666O:$:1+++22222+++1:$:O66666",
"6666666+$=13 4222224 31=$+666666",
"66666666+=15 ,22222, 51=+6666666",
"666666666+:1**22222**1:+66666666",
"666666666o+:122222221:+o66666666",
"66666666o#o+=:11111:=+o#o6666666",
"6666666o#%%o+++++++++o%%#o666666",
"6666666o%&%##o%&#&%o##%&%o666666",
"666666o#%%o#%&o###o&%#o%%#o66666",
"666666;%%%o#&&&#>&&###o%%%;66666",
"66666o#%%#o#&&&#%&&&&#o#%%#o6666",
"66666+22=oo#&&&#>&&&&#oo=22+6666",
"66666+21+6oo%&&#%&&&%oo6+12+6666",
"666666++66o#ooooooooo#o66++66666",
"66666666666#%&%ooo%&%#6666666666",
"66666666666o%&%o6o%&%o6666666666",
"66666666666.#%#.6.#%#.6666666666",
"66666666666.@@X.6.X@@.6666666666",
"666666666666...666...66666666666"
};

이런 파일이

Alt text

이런식으로 창에 뜬다.

int    mlx_hook(void *win_ptr, int x_event, int x_mask,
                 int (*funct)(), void *param);

이벤트 발생 시 미리 정해놓은 함수를 실행시킨다. 이곳을 보면 여러 이벤트들이 나오는데, 이 중 우리가 사용하는것은 KeyPress, KeyRelease, DestroyNotify 정도이다. 키보드를 누를 때 혹은 눌렀다 손을 뗄 때, 창을 종료시킬때 정도면 so_long의 움직임을 모두 구현할 수 있다.

int    mlx_loop (void *mlx_ptr);

무한루프 함수이다. 이 함수를 마지막에 써 주어야 이벤트들을 받을 수 있고, 사용자가 정의한 종료방법에 의해서만 코드가 정상 종료되도록 설정할 수 있다.

int    mlx_string_put(void *mlx_ptr, void *win_ptr, int x, int y, int color,
               char *string);

보너스를 구현할 때 사용해야 하는 함수이다. 터미널에 걸음 수 출력이 아닌 화면에 걸음 수를 띄우기 위해 사용된다. 이것 또한 직관적이지만 color를 넣어주는 방식이 까다로울 수 있다.

int    create_trgb(int t, int r, int g, int b)
{
    return (t << 24 | r << 16 | g << 8 | b);
}

위 함수를 통해 색상을 조절할 수 있다. 위 함수로 rgb 값을 조절해주면 원하는 색상을 얻을 수 있다. (0, 255, 255, 255)는 흰색, (0, 0, 0, 0)이면 검정색

Chapter 4

Mandatory part

map

so_long의 프로그램이 받아오는 인자는 '.ber'의 확장자를 가지는 맵이다. 대부분의 오류, 아니 모든 오류는 유효하지 않은 맵 형식에 의해 발생한다. 따라서 맵을 판단하는 프로그램 로직을 잘 구현하는것이 so_long프로젝트의 절반정도 비중을 차지한다고 볼 수 있다.

나의 맵 판단 로직을 나열해보자면 다음과 같다.

  • '.ber'인 맵 형식이 들어오는지
  • 맵의 줄 수 파악
  • 첫째 줄 유효한지 검증 + 한 줄에 몇 자가 들어오는지 파악하기
  • 중간 줄 유효한지 검증
  • 마지막 줄 유효한지 검증
  • 모든 줄이 다 유효하다는 가정하에 맵을 한 줄로 받아오기 (2차원의 맵을 1차원으로 수정)
  • 맵을 전체적으로 보면서 수집품, 출구, 시작지점을 서브젝트의 요구대로 포함하고 있는지 확인

에러가 발견된다면 에러메세지를 요구사항에 맞게 출력해야한다.

display

맵 검증이 끝났다면 화면에 그림을 띄워야한다. 그림은 한줄로 저장한 맵을 차례대로 읽어 부분부분 창에 띄워주면 된다. 이 때, 가지고 온 이미지들이 같은 규격이면 더욱 표현하기 좋다.

Alt text

mlx라이브러리를 적절히 사용해줬다면 이런식으로 화면을 띄울 수 있다.

gameplay

이제 키보드를 통해 이벤트를 받아 함수를 실행시켜야한다. w를 입력했을 때 w가 입력된 것을 감지하여 캐릭터를 한 칸 위로 올려야 한다.

Alt text

한칸을 위로 올리기 위해서는 캐릭터의 윗 칸이 벽이 아닌지, 수집품을 다 모으지 않은 상태의 출구가 아닌지를 확인하고, 그 외의 상황일 경우에만 움직일 수 있다. 움직일 때 한 줄로 받아온 맵의 문자열을 바꿔가면서 움직이므로 수집품을 획득할 때는 'C'를 'P'로 바꿔주기만 하면 된다. 그리고 수집품의 수량을 확인해놓은 다음, 모두 획득했다면 출구로 갔을 때 종료할 수 있도록 해준다.

Alt text

터미널에 걸음 수를 출력해주면 mandatory 완료.

Chapter 5

Bonus part

sprite animation

보너스 파트는 약간 어려울 수 있다. sprite animation을 요구하는데, 이것은 한 캐릭터로 여러개의 이미지를 관리해서 마치 움직이듯이 화면에 띄워보라는 것을 의미한다.

sprite animation을 효율적으로 하기 위해선, 이미지 포인터를 배열로 관리하는 방법이 있다.

그러면 이미지를 관리하는 구조체가 살짝 달라지게 된다.

# mandatory
typedef struct s_obj
{
    void    *ld;
    void    *tr;
    void    *it;
    void    *d1;
    void    *d2;
    void    *s1;
    void    *s4;
    void    *s7;
    void    *s10;
}                t_obj;

# bonus
typedef struct s_img
{
    void        *pt;
}                t_img;

typedef struct s_obj
{
    t_img        item[1];
    t_img        land[4];
    t_img        tree[4];
    t_img        door[2];
    t_img        sw[3];
    t_img        ss[3];
    t_img        sa[3];
    t_img        sd[3];
    t_img        tt[12];
}                t_obj;

위와 같이 이미지 포인터 배열을 가지는 구조체로 바뀌게 된다. 나의 경우 상하좌우 방향 별로 3개의 이미지를 배열로 만들어 순차적으로 이미지를 띄워주었다. 움직임을 자연스럽게 하기 위해서 배열 내부에서 픽셀단위로 체크하는 변수를 만들어 한 칸이 64픽셀 기준 8픽셀씩 움직이며 동작을 바꿔주는 형태로 구현하였다.

위의 방식이면 d 방향으로 8번 눌렀을 때, 64픽셀을 이동하여 멥의 배열을 변경해주는 식으로 구현했다.

Alt text

enemy

적 구현은 기존 맵의 구성요소에 적이 포함되지 않기 때문에 맵 유효성을 해치지 않으면서 적을 구현해야한다고 생각했다. 따라서 '0'인 맵에 임의로 적을 생성하는 방법을 생각했다. 맵의 행과 열을 곱해주면 맵의 전체 칸 수가 나오는데, 난수를 생성해서 전체 칸 수로 나눠준 나머지를 맵의 인덱스로 사용해서 그 칸이 '0'일 경우 적으로 바꾸어 주는 방식으로 진행했다.

적의 움직임 역시 난수를 활용했다. 보너스 과제는 기본 허용 함수 이외의 함수도 사용할 수 있기 때문에 rand()라는 함수를 사용했고, 이것으로 적이 이동할 방향을 결정할 수 있었다. 캐릭터가 한번에 8픽셀만큼 움직이기 때문에 한 칸을 움직이려면 8번을 움직여야 한다. 따라서 적은 캐릭터가 8번 움직일 때마다 한 칸씩 무작위로 이동하도록 구현했다.

Movement count on display

화면에 움직인 수를 띄우는 것은 매우 쉽다. mlx_string_put 함수를 이용하여 문자열을 띄울 위치, 색상, 띄울 문자를 정해주면 된다. 참고로 문자열을 만들 때, itoa 함수를 사용할텐데 메모리 누수에 주의하도록 하자.

느낀 점

이번 과제는 굉장히 꾸미는데 심혈을 기울인 것 같다. 마음에 드는 캐릭터를 고민하는 것도 시간이 좀 걸렸기 때문이다. 42서울에서의 첫 그래픽 과제였는데, 생각보다 재미있는 과제여서 다음 그래픽 과제인 4서클의 cub3d도 얼른 해보고 싶어졌다.

728x90

'42SEOUL > Circle2' 카테고리의 다른 글

[42SEOUL] pipex  (0) 2023.02.18
[42SEOUL] push_swap  (0) 2023.02.18
  1. Chapter 1
  2. Foreword
  3. Chapter 2
  4. Goals
  5. Chapter 3
  6. Common Instructions
  7. Chapter 4
  8. Mandatory part
  9. Chapter 5
  10. Bonus part
  11. 느낀 점
'42SEOUL/Circle2' 카테고리의 다른 글
  • [42SEOUL] pipex
  • [42SEOUL] push_swap
chanwoong1
chanwoong1
안녕하세요.
WOONGTECH안녕하세요.
250x250
chanwoong1
WOONGTECH
chanwoong1
전체
오늘
어제
  • 분류 전체보기 (231)
    • 42SEOUL (28)
      • Circle0 (1)
      • Circle1 (3)
      • Circle2 (3)
      • Circle3 (2)
      • Circle4 (7)
      • Circle5 (8)
      • Circle6 (4)
    • Algorithm (163)
      • PS (159)
      • Study (4)
    • Blog (5)
    • 우테코 프리코스 (5)
    • Data Science (1)
    • WEB (27)
      • React (18)
      • Recoil (2)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

인기 글

최근 댓글

최근 글

hELLO · Designed By 정상우.
chanwoong1
[42SEOUL] so_long
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.