===== 들어가며 =====
c와 c++은 개발자가 세심하게 조정할 수 있다는게 장점이자 단점이다. 컴파일 역시 하나 하나 라이브러리를 다 제대로 링크해 주어야 한다.
그런데 프로젝트의 크기가 커지다 보면 컴파일 명령어에 넣어야 할 변수들이 너무 많아지기 마련이다.
그렇기 때문에 makefile을 만들어서 매크로화 하여 컴파일하기 마련이다.
이하에서는 raylib를 이용하여 간단하게 컴파일 기초부터 시작하여 makefile을 만들고, 라이브러리화 하는 것까지 알아 볼 예정이다.
===== 소스파일을 통하여 raylib 실행해 보기 =====
==== 1. git clone ====
[[raylib:raylib_설치_및_실행하기|Raylib 설치 및 실행하기]]에서는 Itch.io에서 raylib 실행파일을 다운받아서 설치했었다. 그런데 사실 같이 딸려오는 notepad++이나 mingw는 우리에게 필요가 없다. 우리는 이미 이러한 것들이 있기 때문이다. 따라서 github에서 raylib의 최신버전을 먼저 가져오겠다.
먼저 아무드라이브에 raylib를 다운 받을 폴더를 만들자. 그 이후에 cmd를 쳐서 명령창으로 간 후 다음의 명령어를 실행한다.
git clone https://github.com/raysan5/raylib.git
github 내에서 [[https://github.com/raysan5/raylib|raylib 리포지터리]]를 찾은 후 코드를 누르면 클론 할 수 있는 git 주소가 나타난다.
{{:raylib:raylibgitclone.png?600|Raylib Git Clone}}
그 주소를 커맨드라인 창에서 git clone 다음에 넣으면 raylib 소스파일이 내가 만들어 둔 폴더에 설치가 된다.
마우스 우클릭을 하여 Git Bash Command로 들어간 후에 위 명령을 실행해도 되지만, 굳이 그렇게 할 필요 없다. 커맨드창에서도 git 명령어는 실행된다.
자 이제 raylib 소스파일을 가져왔다.
==== 2. hello world 파일 컴파일해보기 ====
raylib5 폴더(이하에서는 루트폴더라고 하자)에 exaples/core 폴더에 있는 core_basic_window.c 파일을 복사하자. 이제 이 루트폴더에서 컴파일을 해 볼 것이다. 편의상 core_basic_window.c 파일의 이름을 helloworld.c라고 변경하였다.
c는 gcc.exe, c++은 g++.exe가 컴파일 프로그램이다. 이를 이용해서 컴파일을 해야 한다.
다음 명령어를 넣어 보자.
gcc -Wall -std=c99 -Wno-missing-braces -Wunused-result helloworld.c
-Wall -std=c99 -Wno-missing-braces -Wunused-result
의 플래그는 굳이 지금단계에서 알 필요는 없다. 그냥 관용적으로 넣는다고 생각하면 편하다.
그 의미에 대해 알고 싶으면 raylib에 딸려오는 makefile을 살펴보면 각 플래그 마다의 뜻이 자세히 적혀 있다.
컴파일을 해보면 raylib만의 고유 함수들인 'InitWindow', 'SetTargetFPS' 등이 'undefine' 즉 정의되어 있지 않다고 한다.
==== 3. 라이브러리 추가하기 ====
raylib를 사용하려면 정의 파일인 raylib.h와 raylib그에 대한 구현 파일인 라이브러리 파일이 필요하다.
raylib/src
폴더로 이용한다.
여기에는 라이브러리 파일을 묶을 수 있는 소스파일들이 존재한다. 여기에서 커맨드라인에서 'make' 명령어를 실행해본다.
그러면 폴더 내에 'libraylib.a'라는 파일이 생긴다.
gcc에서 a는 스태틱 라이브러리를, so는 동적 라이브러리를 의미한다.
microsoft visual c에서는 lib가 정정 라이브러리를, dll이 동적 라이브러리를 의미한다고 한다(([[https://stackoverflow.com/questions/9688200/difference-between-shared-objects-so-static-libraries-a-and-dlls-so|스탤오버플로우 참조]])).
모든 라이브러리 파일은 접두어로 lib를 갖는다. 따라서 raylib라는 라이브러리를 만들면 'libraylib.a'라는 파일이 생기는 것이다.
이제 raylib/src 폴더에는 헤더파일들과 libraylib.a 라는 정적 라이브러리 파일이 생겼다. 바로 이 'libraylib.a' 파일을 참조할 것이다.
다음의 명령어를 실행해 본다.
gcc -o helloworld -Wall -std=c99 -Wno-missing-braces -Wunused-result helloworld.c -L/raylib/src -lraylib -lopengl32 -lgdi32 -lwinmm
그러면 /raylib/src 폴더에 있는 'libraylib.a'를 참조하여 컴파일이 성공적으로 완성된 것을 알 수 있따. 따라서 helloworld.exe를 실행시키면 파일이 샐행된다.
==== 4. 관련 라이브러리에 대한 추가 설명 ====
=== 가. 사용하는 라이브러리 ===
근데 사실 위 명령어에서 -lopengl32 는 빼도 된다. 즉,
gcc -o helloworld -Wall -std=c99 -Wno-missing-braces -Wunused-result helloworld.c -L/raylib/src -lraylib -lgdi32 -lwinmm
라고만 적어도 된다. helloworld.c에 쓰이는 drawtext() 함수는는 opengl을 전혀 이용하지 않는다는 것을 알 수 있다.
윈도우의 레거시 그래픽 구동 라이브러리인 gdi32를 이용하는 것이다.
=== 나. 다른 라이브러리들의 위치 ===
그런데 우리는 여기에서 한 가지 물음이 생긴다. gdi32, winmm, opengl32등은 우리가 라이브러리 위치로 설정한 raylib/src 폴더에는 없다. 그런데 어떻게 이것을 포함할 수 있는 것일까?
그 이유는
C:\Windows\System32
폴더가 윈도우 환경에서는 전역경로로 사용될 수 있는 것이 디폴트 값이기 때문이다.
즉, gdi32.dll, winmm.dll, opengl32.dll은 system32 폴더에 있다.
따라서 우리는 이에 대해서는 별도의 라이브러리 위치를 설정할 필요가 없는 것이다.
이를 통해서 raylib는 gdi32.dll, winmm.dll, opengl32.dll 등을 의존하여서 만들어진 것임을 알 수 있다.
이를 응용하면, 굳이 raylib를 거치지 않는다고 하더라도, gdi32.dll 등을 이용하여 그림을 바로 그릴 수도 있긴 하다.
또한 굳이 opengl을 다운받지 않아도 opengl을 이용할 수 있는 것이다(물론 directX 역시 윈도우에 기본적으로 내장되어 있다).
===== raylib 소스파일을 바꿔보기 =====
==== 1. 다양한 비트맵 폰트를 써보기 ====
다음 파일을 다운 받자.
{{ :raylib:multiatlas.zip |한글 비트맵폰트 테스트}}
위 프로젝트는 한개의 파일로 되어 있지만 다양한 폰트를 불러오고, 다양한 한글을 사용하는 예제이다.
위 프로젝트는 루트폴더인 raylib5폴더의 하위폴더인 fontatlas에 있다.
이를 컴파일하려면 다음과 같이 명령어를 넣어줘야 한다.
gcc -o main main.c -Wall -std=c99 -Wno-missing-braces -Wunused-result -L../raylib/src -lraylib -lgdi32 -lwinmm -lopengl32 -I../raylib/src -I../raylib/src/external -I'../raylib/utils'
일단 디렉토리를 한단계위로 올라간 후 다시 raylib/src로 참조해야 하므로 한단계 위로 올라가는 '..'이 넣어진 것이 달라졌다.
그리고 폰트는 stb_image 등을 이용한다. 따라서 '/raylib/src/external' 등을 참조해 줘야 한다.
위와 같이 하여 컴파일 하면 컴파일은 잘 되는데 실행해보면 한글이 다 안 보인다.
raylib의 기본 비트맨 폰트 로더는 폰트 아틀라스를 하나만 로드하기 때문이다. 그렇다면 어떻게 해야 할까?
==== 2. RAYLIB 소스파일 수정하기 ====
'raylib/src'폴더에 가보면 rtext.c라는 파일이 있다.
가장 아래로 스크롤 하면 LoadBMFont라는 함수가 있다.
이를 [[raylib:util:raylib_loadbmfont_extender|Load BitmapFont Extender]]에서 LoadBMFontEx() 내에 있는 코드로 바꾸어 주면 된다.
잘 모르겠으면,
[[https://github.com/DongkunLee/raylib/blob/master/src/rtext.c|rtext.c]]에 있는 LoadBMFont() 메서드 내의 코드로 바꾸어 주면 된다.
==== 3. raylib 재컴파일 ====
이제 rtext.c를 고쳤으므로 libraylib.a라는 정적 라이브러리를 재컴파일 해줘야 한다.
raylib/src 폴더에서 다시 한번 make 명령어를 넣자. 그러면 라이브러리가 재컴파일 된다.
==== 4. 실행 파일 재컴파일 ====
이제 다시 fontatlas폴더로 가서
gcc -o main main.c -Wall -std=c99 -Wno-missing-braces -Wunused-result -L../raylib/src -lraylib -lgdi32 -lwinmm -lopengl32 -I../raylib/src -I../raylib/src/external -I'../raylib/utils'
명령어를 수행하자.
그러면 [[raylib:util:raylib_loadbmfont_extender|Load BitmapFont Extender]] 에서 볼 수 있는 것과 같이. 다양한 한글이 제대로 나온다.
이제 앞으로는 그냥 loadfont()함수만 쓰면 한글도 제대로 잘 나온다.
===== Makefile 만들기 =====
==== 1. 준비사항 ====
makefile은 탭을 인식한다. 따라서 탭이 에디터에서 제대로 입력되게 준비할 필요가 있다.
만약 VS 코드라면 아래 하단 상태표시줄에서 'tab 크기'를 클릭한 후에 '탭을 사용한 들여쓰기(Indent using tab)'으로 설정을 바꾸어 줘야 한다.
관련 [[https://www.youtube.com/watch?v=MOdWPA07bMU|동영상]]을 참조하자.
==== 2. Makefile 만들기 =====
위의 멀티 아틀라스 소스에 대한 Makefile을 만든다고 해보자.
하나의 소스만 컴파일 할 때에는 위와 같이 명령창에서 gcc 혹은 g++(c++로 만들 경우)로 명령어를 넣으면 되므로,
여러가지 소스 파일이 있는 대상 프로젝트를 선택하자. [[raylib:flappybird:다국어_지원하기|플래피 버드]]다국어 지원하기
위 소스의 폴더에 'Makefile'이란 파일을 만들자. 만약 VS Code로 한다고 하면, 위와 같이 탭을 사용한 들여쓰기 설정을 해놓자.
Makefile이 이미 있다면 지우고 새로 만들자.
=== 가. 최초 makefile ===
# Define required raylib variables
RAYLIB_PATH ?= C:/raylib5/raylib/src
# Define compiler path on Windows
COMPILER_PATH ?= C:/raylib/w64devkit/bin
# Build mode for project: DEBUG or RELEASE
BUILD_MODE ?= RELEASE
# Define default C compiler: gcc
# NOTE: define g++ compiler if using C++
CC = g++
# Define default make program: Mingw32-make
MAKE = mingw32-make tetris
# Compile option
CFLAGS += -Wall -std=c++14 -D_DEFAULT_SOURCE -Wno-missing-braces
ifeq ($(BUILD_MODE),DEBUG)
CFLAGS += -g -O0
else
CFLAGS += -s -O1
endif
# Define include paths for required headers
# NOTE: Several external required libraries (stb and others)
INCLUDE_PATHS = -I. -I$(RAYLIB_PATH) -I$(RAYLIB_PATH)/external -I'/utils'
# Define library paths containing required libs.
LDFLAGS = -L. -L$(RAYLIB_PATH)
# Define any libraries required on linking
# if you want to link libraries (libname.so or libname.a), use the -lname
LDLIBS = -lraylib -lopengl32 -lgdi32 -lwinmm
# Define all source files required
SRC_DIR = .
OBJ_DIR = obj
#목적 파일 만들기
OBJS = $(OBJ_DIR)/buttonActionMap.o $(OBJ_DIR)/config.o $(OBJ_DIR)/gui.o $(OBJ_DIR)/logo.o $(OBJ_DIR)/game.o $(OBJ_DIR)/menu.o $(OBJ_DIR)/settingscreen.o $(OBJ_DIR)/main.o
# Default target entry
# NOTE: We call this Makefile target or Makefile.Android target
all:
$(MAKE)
$(OBJ_DIR)/buttonActionMap.o : buttonActionMap.h buttonActionMap.cpp
$(CC) -c buttonActionMap.cpp $(CFLAGS) $(INCLUDE_PATHS) -o $@
$(OBJ_DIR)/config.o : config.h config.cpp
$(CC) -c config.cpp $(CFLAGS) $(INCLUDE_PATHS) -o $@
$(OBJ_DIR)/gui.o : gui.h gui.cpp
$(CC) -c gui.cpp $(CFLAGS) $(INCLUDE_PATHS) -o $@
$(OBJ_DIR)/logo.o : logo.h logo.cpp
$(CC) -c logo.cpp $(CFLAGS) $(INCLUDE_PATHS) -o $@
$(OBJ_DIR)/game.o : game.h game.cpp
$(CC) -c game.cpp $(CFLAGS) $(INCLUDE_PATHS) -o $@
$(OBJ_DIR)/menu.o : menu.h menu.cpp
$(CC) -c menu.cpp $(CFLAGS) $(INCLUDE_PATHS) -o $@
$(OBJ_DIR)/settingscreen.o : settingscreen.h settingscreen.cpp
$(CC) -c settingscreen.cpp $(CFLAGS) $(INCLUDE_PATHS) -o $@
$(OBJ_DIR)/main.o : main.cpp
$(CC) -c main.cpp $(CFLAGS) $(INCLUDE_PATHS) -o $@
#Icon file path
ICON += $(RAYLIB_PATH)/raylib.rc.data
#컴파일
tetris : $(OBJS)
$(CC) $(CFLAGS) $(OBJS) -o tetris $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) $(ICON)
=== 나. 설명 ===
== (가) raylib 라이브러리 ==
우리는 libraylib.a 라는 라이브러리를 이용할 것이다. 따라서 이에 대한 경로를 지정해 줘야 한다.
RAYLIB_PATH ?= C:/raylib5/raylib/src
로 하여 경로를 지정해 주었다.
== (나) 컴파일러 ==
c로 작성한 파일은 gcc로 컴파일 하고, c++로 작성한 파일은 g++로 컴파일 한다.
따라서 컴파일러를 지정해 주어야 한다.
CC = g++
== (다) 디버그 모드/릴리스 모드 ==
디버그 모드와 릴리스모드의 옵션을 설정해 준다. 다음과 같이 설정해주었다.
ifeq ($(BUILD_MODE),DEBUG)
CFLAGS += -g -O0
else
CFLAGS += -s -O1
endif
== (라) 헤더파일과 라이브러리 ==
헤어파일과 라이브러리를 각각 그 경로를 설정해주었다.
== (마) 목적 파일 만들기 ==
각 소스파일들을 기계어로 만들어야 한다.
그런데 우리가 배치파일(혹은 리눅스에서는 쉘 파일)을 만들지 않고 이렇게 Makefile을 만드는 이유는 뭘까?
Makefile을 만들면 이미 만들어진 목적파일은 생략하고 변경된 소스만 다시 목적파일로 만들어 주기 때문이다. 만약 배치파일로 만든다면 목적파일의 변경여부와 상관없이 매번 목적파일을 새롭게 만들것이다.
따라서 목적파일이 그대로 있어야만 한다. 그런데 목적파일이 내 소스파일과 섞이면 지저분하므로, 목적파일을 위한 디렉토리를 따로 만들어주자.
파일탐색기로 'obj'란 디렉토리를 만들자.
그리고 위의 소스에서 보는 바와 같이 각 목적파일은 'obj'라는 폴더 내부에서 목적 파일을 만드는 것이다. 하나만 따로 떼어서 살펴보자.
$(OBJ_DIR)/buttonActionMap.o : buttonActionMap.h buttonActionMap.cpp
$(CC) -c buttonActionMap.cpp $(CFLAGS) $(INCLUDE_PATHS) -o $@
콜론(:)의 왼쪽, 즉 목적파일로 만들어지는 것을 타켓파일이라고 하고 오른쪽에 있는 것을 의존파일이라고 한다.
의존파일을 이용하여 목적파일을 만드는 것이다.
그런데 컴파일러(g++)에서 의존파일의 명칭을 바꾸려면 아웃풋을 뜻하는 -o 플래그 뒤에 그 의존파일의 전체 이름을 넣어야 한다.
우리의 경우 'obj'라는 폴더 아래에 'buttonActionMap.o'라는 목적 파일을 만들 것이다. 이는 'obj/buttonActionMap.o'라고 적을 수 있다.
이렇게 -o 플래그 뒤에 만들어질 목적 파일의 명칭을 적어야 한다.
Makefile에서 타켓파일을 지정한느 매크로는 '$@'이다. 따라서 이를 이용하면 타켓파일을 두번 쓸 필요는 없다.
== (바) 아이콘 ==
윈도우 환경에서 파일의 아이콘을 설정해줄 수도 있다. 우리는 아이콘을 다음과 같이 링크해 줬다.
ICON += $(RAYLIB_PATH)/raylib.rc.data
== (사) 컴파일 하기 ==
이제 만들어진 목적파일들을 링크하여줘야 한다.
#컴파일
tetris : $(OBJS)
$(CC) $(CFLAGS) $(OBJS) -o tetris $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) $(ICON)
tetris라는 이름으로 목적파일들을 컴파일 해준다. 그런데 라이브러리의 링크를 지시하는 플래그들은 맨 뒤에 와야 한다.
따라서 '$(LDFLAGS) $(LDLIBS) $(ICON)'까지는 반드시 맨뒤에 와야 한다. 만약 그렇지 않으면 'undefined ...'하면서 컴파일러는 에러를 내뿜을 것이다.
== (아) 메이크의 대상 지정하기 ==
이렇게 Makefile을 만든 뒤에는 커맨드 창에서 'make' 명령어를 누르면 Makefile에 있는 매크로를 수행하면서 컴파일이 이루어진다.
그런데 make는 지정된 타켓만 make를 한다. 우리는 최종적으로 tetris라는 실행파일을 만들 것인데,
이러한 최종 실행파일은 tetris라는 이름으로 타켓이 지정되어 있다.
따라서 'make tetris'를 해야만 최종 목표인 컴파일까지 한다.
만약 아무런 지정도 안 해주고 단순히 'make'만 누르면 제일 처음에 설정한 타겟인 'buttonActionMap'만 목적파일로 만들어진다.
그런데 이렇게 매번 'make tetris'를 누르는 것도 귀찮기 마련이다. 그냥 'make'만 했으면 좋을것다.
따라서
아예
# Define default make program: Mingw32-make
MAKE = mingw32-make tetris
라고 하여 타겟 자체를 지정해 버리자.
그리고는 이렇게 코드를 짜면 모든 목적파일을 검토 한 후에 tetris라는 최종 파일을 만든다.
all:
$(MAKE)
이제 커맨드 창에서 단순하게 'make'만 누르면 프로젝트 전체의 컴파일이 된다.
===== VS Code의 환경 설정하기 =====
==== 1. task.json 설정하기 ====
우리는 위 Makefile에서 딱 한가지 분기만 만들었다. 디버그와 릴리스 모드 분기다.
따라서 Makefile을 실행시키는 task.json에는 다른 인자들은 설정해 줄 필요 없고, 단지 하나, 디버그 모드에서는
BUILD_MODE=DEBUG
를 넣어주면 된다. 굳이 이를 코드로 보여주면 다음과 같다.
{
"version": "2.0.0",
"tasks": [
{
"label": "build debug",
"type": "process",
"command": "make",
"args": [
"BUILD_MODE=DEBUG"
],
"windows": {
"command": "C:/raylib/w64devkit/bin/mingw32-make.exe",
"args": [
"BUILD_MODE=DEBUG"
]
},
"group": "build",
"problemMatcher": [
"$gcc"
]
},
{
"label": "build release",
"type": "process",
"command": "make",
"args": [ ],
"windows": {
"command": "C:/raylib/w64devkit/bin/mingw32-make.exe",
"args": [ ]
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": false
},
"detail": "compiler: C:\\raylib\\w64devkit\\bin\\g++.exe"
}
]
}
==== 2. launch.json ====
실행할 프로그램의 경로를 정확히 설정해 주면 된다.
우리는 tetris.exe 하나로만 실행파일을 만들 것이므로,
"program": "${workspaceFolder}/tetris",
이렇게만 바꾸어 주면 된다.
==== 3. c_cpp_properties.json ====
에디터가 인텔리센스를 보여줄 헤더의 위치를 설정해주는 것이다.
다음과 같이 해당 경로를 설정해 주면 된다.
"includePath": [
"C:/raylib5/raylib/src/**",
"${workspaceFolder}/**",
"${workspaceFolder}/utils/**"
],
===== 더 읽어보기 =====
[[raylib:정적_라이브러리_만들기|C / C++ 에서 정적라이브러리 만들기]]도 읽어보자