목차

  

raylib 메인메뉴 화면

스타팅 파일

GUI 클래스 만들기

1. 목표

간단한 버튼 GUI를 만들 것이다.

뭐 별거 없다. 박스를 만들고, 그 안에 가운데 정렬로 글자를 넣을 것이다. 그리고 마우스가 박스 안에 들어가면 버튼의 모양을 바꿀 것이고, 박스가 선택된 상태에서 마우스 왼쪽 버튼을 누르면 해당 기능을 수행하게 할 것이다.

이게 간단 버튼 GUI의 기능일 것이다. 이러한 목표를 염두에 두고 만들어 보자

2. gui.h

gui.h
#pragma once
#include <string>
#include "raylib.h" 
 
class GUI
{
    public:
        void Init(int button_x, int button_y, int button_width, int button_height, std::string button_title);
        void Update(); 
        void Draw();
        void Tick();
        bool GetChecked();
 
    private:
        int x, y, width, height;
        std::string title;
        bool isChecked = false;
        Rectangle rect;
 
};

버튼을 그리기 위해, 4개의 변수가 필요하다. 그런데 따로 레이라이브의 내장 자료형인 Rectangle도 사용했다.

왜냐하면 향후에 박스의 선을 그리는데 필요한 레이라이브 내장 함수인 DrawRectangleLinesEx()을 이용할 것인데,

여기에는 Rectangle변수를 인자로 받기 때문이다.

Rectangle자료형은 4개의 변수가 모두 플로트 이므로 int를 플로트로 변환하는 과정이 필요하다.

3. gui.cpp

gui.cpp
#include "gui.h"
 
 
Vector2 mousePoint = { 0.0f, 0.0f };
 
 
void GUI::Init(int button_x, int button_y, int button_width, int button_height, std::string button_title)
{
    x = button_x;
    y = button_y;
    width = button_width;
    height = button_height;
    title = button_title;
    rect = Rectangle{float(x), float(y), float(width), float(height)};
}
 
bool GUI::GetChecked()
{
    return isChecked;
}
 
void GUI::Update()
{
    mousePoint = GetMousePosition();
 
    if (CheckCollisionPointRec(mousePoint, rect)) isChecked = true;
     else isChecked = false; 
}
 
void GUI::Draw()
{
    DrawRectangle(x, y, width, height, DARKGREEN);   // 버튼을 그린다. 
    int length = MeasureText(title.c_str(), 20);     // 글자의 포인트는 20으로 가정할 것이다.  20으로 가정한 글자의 가로 픽셀 크기를 구한다. 
 
    DrawText(title.c_str(), x  + (width / 2) - (length / 2), y + (height /2) - (20 / 2), 20, DARKBROWN); // 글자를 가로 및 세로 가운데 정렬한다. 
 
    if (isChecked)
    {
        DrawRectangleLinesEx(rect, 6, Fade(BLACK, 0.3f));
    }
}
 
void GUI::Tick()
{
    BeginDrawing();    
    Update();
    Draw();
    EndDrawing(); 
}

가. 버튼 생성 - Init() 함수

버튼의 구성요소인 x, y, width, height 값을 초기화한다.

그리고 이미 언급하였듯 float로 변환한 값을 Rectangle로도 저장하였다.

나. 버튼 그리기

버튼 자체만 그릴 때에는 raylib의 DrawRectangle()함수는 int형을 인자로 받으므로 각각의 x, y, width, height값을 넣어주면 된다1).

이제 글자를 버튼의 가장자리에 넣게 해보자.

버튼의 가로 가운데에서 단어의 가로 크기의 반만큼을 왼쪽으로 옮기면 단어가 정중앙에 위치할 것이다.

그런데 우리는 단어의 가로 크기를 모른다. 글자 포인트를 예로들어 20으로 한다고 하더라도, 글자의 가로 픽셀은 각 글자마다 다르기 떄문이다. 이럴 때에는 레이라이브에서 내장 함수로

  int MeasureText(const char *text, int fontSize);     //  Measure string width for default font

를 제공한다. 따라서 MeasureText로 단어의 가로 크기를 구할 수 있으며, 이를 토대로 단어의 가로 크기의 2분의 1을 구할 수 있다.

박스의 가운데에서 단어의 가로 크기의 반을 빼면 박스에서 글자가 시작할 위치가 될 것이다.

세로의 경우에는 20포인트를 알고 있으므로, 박스의 height 값을 이용하면 세로 가운데가 될 것이다.

여기에 x와 y의 값을 더하면 버튼에 가로 정렬된 글자를 새길 수 있다.

다. 마우스 위치 저장 하기

Update()함수에는 마우스 위치 값을 매번 저장하였다.

GetMousePosition()함수는 Vector2 값을 리턴한다.

라. 마우스 포인터가 버튼에 있는지 여부

박스와 포인터간의 컬리젼을 체크하는 함수는

    bool CheckCollisionPointRec(Vector2 point, Rectangle rec);  // Check if point is inside rectangle

이다. 이를통해 포인터가 박스 안에 있음을 확인할 수 있다.

만약 마우스 포인터가 박스 안에 있으면 isChecked를 true 값으로 리턴한다.

마. 버튼이 선택되었을 경우 버튼의 테두리를 굵게 하기

박스의 외곽선을 그리는 raylib 내장 함수는 다음과 같다.

void DrawRectangleLinesEx(Rectangle rec, float lineThick, Color color);   // Draw rectangle outline with extended parameters

이를 이용하여 Draw()함수 내에서 isChecked가 true일 때 외곽선을 그리면 된다.

그러면 버튼이 선택되 것을 구현할 수 있다.

1. 버튼 선언하기

menu.cpp의 최상단에 다음과 같이 버튼을 선언한다. 버튼을 3개만 그릴 것이므로 배열로 3개를 선언한다.

// GUI button; 
GUI button[3];

2. 버튼 초기화하기

메뉴에는 '게임하기', '점수화면 보기', '게이종료하기'의 3가지 버튼이 있게 만들 것이다.

그러면 다음과 같이 void menu::Init() 메서드에 각 버튼을 선언할 수 있을 것이다.

void Menu::Init(int width, int height, std::string title)
{
 
    SetWindowSize(width, height);
    SetWindowTitle(title.c_str());
 
   button[0].Init(GetScreenWidth() - 200, GetScreenHeight() - 200, 180, 40, "Game Start");
   button[1].Init(GetScreenWidth() - 200, GetScreenHeight() - 140, 180, 40, "Score Board");
   button[2].Init(GetScreenWidth() - 200, GetScreenHeight() - 80, 180, 40, "Exit Game");
}

3. 버튼 그리기

Draw 메서드에 다음과 같이 버튼을 그리자

void Menu::Draw()
{
    ClearBackground(BLACK);
    DrawText("Flappy Bird Menu!", GetScreenWidth() / 2 - 160, GetScreenHeight() / 2  - 40, 40, DARKBROWN);
 
   for (int i = 0; i < 3;i++)
   {
        button[i].Draw();
   }
}

4. 버튼을 왼쪽 클릭으로 선택하면 기능 수행하기

다음과 같이 하면 게임실행과 게임종료의 기능을 수행할 수 있을 것이다.

마우스 왼쪽 버튼 클릭은

IsMouseButtonPressed(MOUSE_BUTTON_LEFT)

이다.

void Menu::Update()
{
    for (int i = 0; i < 3; i++)
    {
        button[i].Update();
    }
 
    if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && button[0].GetChecked()) menuManager->scene = Scene::game; 
 
    if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && button[1].GetChecked())  menuManager->scene = Scene::scoreScene; 
 
    if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && button[2].GetChecked())  menuManager->gameExit = true;
 
}

버튼이 선택된 상태에서 마우스 왼쪽버튼을 누르면 우리 전에 만들었던 씬메니저의 상태를 바꾸도록 하였다.

main.cpp 에서 루프 분기 수정하기

로고화면 다음에는 메인메뉴가 나오게 만들어야 하고, 메뉴매니저의 scene 변수가 Scene::game으로 바뀌면 게임 화면이 나오게 해야 한다.

따라서 while 루프는 다음과 같이 바뀔 것이다.

        switch (menuManager->scene) 
        {
            case Scene::logo :   // 로고 화면 
                if (menuManager->lastSce != menuManager->scene)  // 로고화면으로 전환시에 로고 클래스 초기화 하기 
                {
                    logo.Init(screenWidth, screenHeight, "Flappy Bird Logo Screen", 120);
                    menuManager->lastSce = menuManager->scene;    // 마지막 씬을 현재 씬으로 저장하여 앞으로 씬 전환이 아니라고 알려주기 
                }else
                {              
                    if(logo.timer < logo.MaxTimer)
                    {
                        logo.Tick();    // 설정된 시간 동안 루프를 돌린다. 
                    }else
                    {
                        menuManager->scene = Scene::titleMenu;  // 설정된 시간을 넘기면 게임 화면으로 넘어간다. 
                    }
                }
            break;
 
            case Scene::titleMenu :
                if (menuManager->lastSce != menuManager->scene) // 다른 씬에서 게임화면으로 전환하면 클래스를 초기화한다. 
                {
                    menu.Init(screenWidth, screenHeight, "Flappy Bird Main Menu");  // 게임화면을 초기화한다. 로고화면과 다르다는 것을 알리기 위해 화면을 크게 했다. 
                    menuManager->lastSce = menuManager->scene;   // 마지막 씬도 현재 씬과 똑같다고 설정한다. 
                }else
                {
                  menu.Tick();   // 게임 루프를 돌린다. 
                }
            break;
 
 
            case Scene::game :
                if (menuManager->lastSce != menuManager->scene) // 다른 씬에서 게임화면으로 전환하면 클래스를 초기화한다. 
                {
                    game.Init(GamescreenWidth, GamescreenHeight, "Flappy Bird");  // 게임화면을 초기화한다. 로고화면과 다르다는 것을 알리기 위해 화면을 크게 했다. 
                    menuManager->lastSce = menuManager->scene;   // 마지막 씬도 현재 씬과 똑같다고 설정한다. 
                }else
                {
                  game.Tick();   // 게임 루프를 돌린다. 
                }
            break;
 
 
            default :
 
            break;
        }

텍스쳐 그림 파일 그리기

1. 의의

지금까지는 raylib의 함수를 이용해서 박스나 원등을 그렸다. 그런데 실제 게임을 만들려면 그림 파일을 게임화면에 올려야 한다.

따라서 DrawTexture란 함수를 이용하여 그림 파일을 화면 내에 그릴 것이다.

그림을 그릴 에셋 파일은 다음과 같다.

에셋 파일

2. Texture 선언하기

우리는 클래스 전역에서 texture를 이용할 것이므로, menu.cpp에서 최상단에 다음과 같이 텍스쳐를 선언하자

// 텍스처 로드하기 
// NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required)
Texture2D birdImage;        // Texture loading;  

3. 파일 로드하기

텍스쳐 파일을 로드하려면 다음과 같이 하면 된다.

Init() 메서드 내에 넣으면 된다.

   birdImage = LoadTexture("Assets/birddrawing.png");        // Texture loading

4. 텍스쳐 그리기

다음과 같이 Draw메소드 내에 DrawTexure함수를 구현한다.

   DrawTexture(birdImage, GetScreenWidth() / 2, 80, WHITE);

5. 텍스쳐 메모리에서 제거하기

클래스가 소멸하면 텍스쳐를 메모리에서 제거하는 것을 빼먹지 말도록 하자

Menu::~Menu()
{
    UnloadTexture(birdImage);       // Texture unloading
}

6. 정리

지금까지 작성한 결과물은 다음과 같다.

raylib 메인메뉴 화면

소스 코드는 다음과 같다.

GUI와 메인메뉴 소스파일

다음에는 Raylib로 한글 폰트 출력하기를 해볼 것이다.

1)
raylib는 int형과 float형을 섞어 써서 좀 짜증이 나긴하다