발전하는 춘배

[원고, SDL3](完) 6. 윈도우 오목 게임 만들기 - 메뉴화면, 종료화면, 게임 상태의 표현, cmake 써먹어보기, 컴파일 옵션들 이해해보기 본문

.원고

[원고, SDL3](完) 6. 윈도우 오목 게임 만들기 - 메뉴화면, 종료화면, 게임 상태의 표현, cmake 써먹어보기, 컴파일 옵션들 이해해보기

춘배0 2025. 8. 25. 22:48

목표는 기본 메뉴화면과, 게임 종료 시 누가 이겼는지를 표시하는 종료 화면을 띄우는 거다. 기본 메뉴화면엔 게임 시작 버튼이 있고, 게임 종료 화면엔 다시하기 버튼을 만들어 보자.

 

시작은 gpt형님한테 도움을 받아본다.

게임 상태(state) 개념을 도입하는 게 제일 깔끔합니다.

라고 한다. 오 이런 개발 경험이 없는 사람 입장에서 gpt형님은 이런 게 가장 좋은 거 같다. 이런 식으로 할 수 있구나~를 제시해주니깐.

 

enum class GameState {
    MENU,   // 초기 화면
    PLAYING, // 실제 게임
    OVER
};

enum으로 게임 상태들을 정의한다.

class Game
{
private:
    GameState state;
    const SDL_FRect startButton = { 400, 550, 400, 100 }; // x,y,w,h
    ...
}


...

Game::Game(User user1, User user2) : state(GameState::MENU), user1(user1), user2(user2)
{
    board = InitializeBoard();
    gameOver = new GameOver();
    isUser1Turn = true;
}

Game 클래스에 멤버로 추가한다. 생성자에서 초기화해준다.

추가로 버튼도 정의해준다.

 

아이디어는 이렇다.

매 프레임 일어나는 일(별거없음): 이벤트 스택 쌓인 거 처리(game.HandleEvent(e);) -> 렌더링(game.Render(renderer);) -> SDL_RenderPresent(renderer);

사실상 함수2개 호출이 다다.

모든 함수에서 state에 따라 분기시켜서 다른 걸 하게 하면 된다. 예를 들어

void Game::HandleEvent(const SDL_Event& e) {
    if (state == GameState::MENU) {
        if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
            int mx = e.button.x;
            int my = e.button.y;
            if (mx >= startButton.x && mx <= startButton.x + startButton.w &&
                my >= startButton.y && my <= startButton.y + startButton.h) {
                state = GameState::PLAYING; // 게임 시작
            }
        }
    } else if (state == GameState::PLAYING) {
        if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
            // 게임로직
        }
    } else if (state == GameState::OVER) {
        if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
            // 메뉴랑 사실상 동일
        }
    }
}

요런 식으로 state별로 다른 로직을 짜두는거다. 쉽다.

렌더링도 MENU state라면 메인화면 뿌려주고, PLAYING state라면 바둑판 뿌려주고 해주면 된다. 쉽다.

MENU(시작) 화면
PLAYING 화면
OVER(종료)화면. 흑돌이 승리해서 배경색 까망

 

이대로 끝낼까 하다가 그래도 게임인데 텍스트 GUI정도는 있어야하는 게 아닌가 해서 알아보니 SDL_ttf 라이브러리가 따로 있다고.

귀찮지만 해보기로 했다.

https://github.com/libsdl-org/SDL_ttf/releases

 

Releases · libsdl-org/SDL_ttf

Support for TrueType (.ttf) font files with Simple Directmedia Layer. - libsdl-org/SDL_ttf

github.com

역시 라이브러리 설치 및 적용이 젤 어렵다.

처음 SDL3 설치했을 때처럼 zip파일 받아서 압축풀고 docs에 있는 방법대로 cmake 이용해서 빌드하려 했는데 실패. 이유는 SDL3을 못찾음.

gpt한테 물어봐서

cmake -S . -B build -DCMAKE_PREFIX_PATH="C:/SDL3/x86_64-w64-mingw32"

이렇게 했더니 SDL3 헤더랑 라이브러리 찾을 수 있게 됨.

바로 다음 문제: FreeType 의존성이 있기 때문에 이거도 설치해야함.

https://download.savannah.gnu.org/releases/freetype/?C=M&O=A

 

Index of /releases/freetype/

 

download.savannah.gnu.org

여기서 최신꺼 zip파일 설치해줌 압축풀고

mkdir build
cd build
cmake .. -G "MinGW Makefiles" -DCMAKE_INSTALL_PREFIX=C:/freetype
cmake --build . --config Release
cmake --install . --config Release

이렇게 했더니 C:/freetype에 include랑 lib 생겼음. 신기하다 cmake

드디어 SDL3_fft도 빌드가 가능해짐.

cmake -S . -B build -DCMAKE_PREFIX_PATH="C:/SDL3/x86_64-w64-mingw32;C:/freetype" -DCMAKE_INSTALL_PREFIX="C:/SDL3/SDL3_ttf-3.2.2/install" -DCMAKE_BUILD_TYPE=Release
cmake --build build

 

빌드성공

 

다시 오목프로젝트로 돌아와서

SDL3_ttf.dll 붙여주고

g++ src\*.cpp -o main.exe -Iinclude -I "C:\SDL3\x86_64-w64-mingw32\include" -I "C:\SDL3\SDL3_ttf-3.2.2\include" -L "C:\SDL3\x86_64-w64-mingw32\lib" -L "C:\SDL3\SDL3_ttf-3.2.2\build" -lSDL3 -lSDL3_ttf

경로 덕지덕지 붙여서 컴파일 성공.

하다보니깐 감이 온다.

알게된거:

-I 옵션: .h 헤더파일 들어 있는 경로 추가해줌. 컴파일러가 함수 선언을 알 수 있게 됨. 근데 -L 안 붙이면 링커가 함수 구현부를 못찾음.

-L 옵션: 함수 구현이 들어 있는 경로를 추가해줌. .a(정적 라이브러리), .dll(동적 라이브러리) 같은 파일들

-l 옵션: -L로 붙은 위치에서 실제로 어떤 라이브러리를 쓸 지 결정. 링커가 lib<name>.a 또는 lib<name>.dll.a 파일을 찾아서 연결해줌. 그니깐 위 커맨드로 컴파일하면 libSDL3.dll.a랑 libSDL3_ttf.dll.a를 써먹겠단 뜻. 

디렉터리 들어가보니까 실제로 있었다! 깨달음을 얻었따..

 

아 그럼 굳이 omok디렉토리 안에 .dll을 복붙할 필요는 없었구만. 왜했더라 이거 생각해보면 처음에 뭐 모르니깐 gpt가 하란대로 했던 것 같다. 과감히 삭제. 했더니 이게왠걸 컴파일은 되는데 실행이안된다. 이유는?

.dll.a (임포트 라이브러리): 링커가 “이 함수는 DLL에 있다”라고 알려주는 역할.

그니깐 -lSDL3으로 써먹은 libSDL3.dll.a는 그 자체로 함수 구현이 들어있는 게 아니라 런타임에 필요하면 dll에서 갖다 써라~는 느낌. 

아하 완벽히 이해했다.
마지막으로 궁금한 거. main.exe가 들어 있는 디렉토리와 동일한 디렉토리에 실제 dll파일이 있어야 하는 건지? 아니면 디렉토리가 달라도 쓸 수 있는 건지?

GPT형님의 답변:

  • Windows는 LoadLibrary 규칙에 따라 DLL을 찾음:
  1. 실행 파일과 동일한 디렉토리 → 가장 우선
  2. Windows 시스템 디렉토리 (C:\Windows\System32)
  3. 현재 작업 디렉토리
  4. PATH 환경 변수에 등록된 디렉토리

그렇구만 그냥 디렉토리에 복붙해놓는 게 젤 맘편하겠다. 고민해결!

 

 

 

이제 후딱 실제 text 출력하고 끝내자.

https://www.freepascal-meets-sdl.net/rendering-text-with-fonts-sdl3_ttf-in-sdl3/

 

Rendering Text with Fonts (SDL3_ttf) - Free Pascal meets SDL

It is shown how text can be rendered by SDL3_ttf in Free Pascal using True Type Fonts.

www.freepascal-meets-sdl.net

그냥은 또 안 되고 이런 과정을 거쳐야 한다. 쉬운 일은 없구나. 그래도 이미지 보니까 이해가 좀 잘 된다.

https://infoarts.tistory.com/59

 

SDL2_ttf 사용하기

제가 SDL 라이브러리와 함께 많이 쓰는 라이브러리에는 SDL_image, SDL_ttf, SDL_net이 있습니다. 그중에 SDL2_ttf를 이용하여 FreeType 폰트를 출력하는 방법을 알아보겠습니다. SDL2_ttf 사이트: https://www.libsdl

infoarts.tistory.com

코드는 이거 참고해서 붙여넣었다. SDL3으로 넘어오면서 바뀐거 적용시켜서 성공.

 

 

 

 

이렇게 조잡한 오목게임 만들어봤따.

이룬 것

- SDL3 써먹어보기 (특히 Video, Event 카테고리의 API 맛보기 성공)

- SDL3_ttf 써먹어보기

- 그 과정에서 C/C++ 프로젝트에서 라이브러리 가져다 쓰는 법 부딪히면서 배움

- 컴파일-링킹 과정 상기. 특히 -I, -L, -l 옵션, .h, .a, .dll.a, .dll 확장자 이해.

- cmake 써먹어보기 (이해는1도못했음 사실) 그래도 써먹어봤다는 게 중요한 게 아닐까요?

- C++플젝 구조: include에 헤더파일 몰아넣고 src에 구현 몰아넣는 구조 써봄

- enum으로 (게임) 상태를 표현하고 그에 따라 분기시킨다는 아이디어 획득

 

요종도? 나름 재미있었따.

반응형