사용자 도구

사이트 도구


raylib:flappybird:slider_gui_만들기
slider gui 만들기

Raylib Image Slider and CheckBox

  

스타팅 프로젝트

참고 : 사용한 슬라이더 UI는 open game art에 있는 RPG GUI construction kit v1.0이다.

Slider Gui 만들기

1. gui. h

기존에 만들었떤 button gui를 상속하여 슬라이더를 만들 것이다. 헤더 파일은 다음과 같다.

gui.h
#pragma once
#include <string>
#include "raylib.h" 
#include "config.h"
#include "buttonActionMap.h"
 
class GUI
{
    public:
        GUI();
        GUI(int button_x, int button_y, int button_width, int button_height, std::string button_title, Font*  _font);
        virtual bool Update(); 
        virtual void Draw();
        void Tick();
        bool GetChecked();
        void Checked();
        void UnChecked();
 
    protected:
        int x, y, width, height;
        std::string title;
        bool isChecked = false;
        Rectangle rect;
        Font* font;
};
 
 
class GUISlider : public GUI
{
    private:
        float padding;
        Rectangle leftButton;
        Rectangle rightButton;
 
        float *sliderValue;
        bool changeValueMode = false;
        void ValueUp();
        void ValueDown();
 
    public:
        enum class arrowCheck {
            none = 0, left, right, slider
        }; 
        GUISlider();
 
        GUISlider(int slider_x, int slider_y, int slider_width, int slider_height, std::string slider_title, Font *_font, float *value);
        virtual bool Update(); 
        virtual void Draw();
        bool MouseSliding();
 
        arrowCheck arrow = arrowCheck::none;
};

가. 좌우 버튼 추가

슬라이더의 좌우에는 입력 값(예를 들어 볼륨)을 줄여주거나 크게 해주는 버튼을 만들 것이다. 따라서 left button과 right button을 추가했다.

나. virtual 전치사

상속의 다향성을 이용할 것이다. slider 객체는 slider에 해당하는 메서드를 실행하게 하고, button 객체는 button에 해당하는 메서드를 실행하게 할 것이다. 따라서 update()와 draw()는 virtual로 선언하여 각기 다른 로직을 실행하게 했다.

부모 클래스인 gui 클래스1)도 이러한 이렇게 바뀌었다. 따라서 기존의 gui클래스도 이에 맞게 수정해주어야 한다. 따라서 파일 전체를 올려 놓았다.

다. arrow check

슬라이더는 슬라이더 본체와 왼쪽 버튼, 오른쪽 버튼의 세개가 있다. 마우스가 어느 지점을 가리키는 지를 알아내기 위하여 arrow check라는 enum을 만들었다.

라. 생성자

기존에는 Init()라는 함수로 객체를 초기화하였는데, 굳이 이렇게 할 필요 없이 생성과 동시에 기본 값을 넣어주면 될 것이다. 따라서 Init()는 모두 없애고 생성자에 인자를 넣어주게 코드를 짰다. 이러면 호출하는 쪽에서 코드가 더 간결해 질 것이다.

2. gui.cpp

전체 소스파일은 다음과 같다.

gui.cpp
#include "gui.h"
 
 
Vector2 mousePoint = { 0.0f, 0.0f };
 
GUI::GUI()
{}
 
GUI::GUI(int button_x, int button_y, int button_width, int button_height, std::string button_title, Font*  _font)
{
    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)};
    font = _font;
}
 
bool GUI::GetChecked()   
{
    return isChecked;
}
 
void GUI::Checked()    // 현재 버튼이 체크된 상태로 하기 
{
    isChecked = true;
    SetMousePosition(x + width / 2, y+height/2);  // 마우스 커서를 현재 버튼의 정중앙에 넣기 
}
 
void GUI::UnChecked()  // 현재 버튼이 체크 해제된 상태로 하기 
{
    isChecked = false;
}
 
bool GUI::Update()  // 마우스가 버튼 위에 위치해 있는지를 체크
{
    mousePoint = GetMousePosition();
 
    if (CheckCollisionPointRec(mousePoint, rect)) isChecked = true;
     else isChecked = false; 
 
    return false;
}
 
void GUI::Draw()  // 버튼 그리기
{
    DrawRectangle(x, y, width, height, DARKGREEN);   // 버튼을 그린다. 
 
    Vector2 length = MeasureTextEx(*font, title.c_str(), 32, 5); // 글자의 포인트는 32으로 가정할 것이다.  32으로 가정한 글자의 가로 픽셀 크기를 구한다. 
 
    // 텍스트 출력
    Vector2 pos = {(float)x  + (width / 2) - (length.x / 2), (float)y + (height /2) - (length.y / 2)};     // 글자를 가로 및 세로 가운데 정렬한다. 
    DrawTextEx(*font, title.c_str(), pos, 32, 5, DARKBROWN);  
 
    if (isChecked)
    {
        DrawRectangle(x, y, width, height, Color{255, 255, 255, 85});   // 버튼을 그린다. 
        DrawRectangleLinesEx(rect, 4, Fade(BLACK, 0.3f));         // 버튼 꾸미기
    }
}
 
void GUI::Tick()
{
    BeginDrawing();    
    Update();
    Draw();
    EndDrawing(); 
}
 
GUISlider::GUISlider() {}
 
GUISlider::GUISlider(int slider_x, int slider_y, int slider_width, int slider_height, std::string slider_title, Font* _font, float *value)
{
    x = slider_x;
    y = slider_y;
    width = slider_width;
    height = slider_height;
    title = slider_title;
    rect = Rectangle{float(x), float(y), float(width), float(height)};
    font = _font; 
    padding = 20.0f;
    leftButton = Rectangle{float(x) - padding -float(height), float(y), float(height), float(height) };
    rightButton = Rectangle{float(x) + float(width) + padding, float(y), float(height), float(height)};
    sliderValue = value;  // 슬라이더 값은 주소로 가져온다. 호출하는 쪽에서 값이 바뀌면 슬라이더 내부의 값도 바뀐다. 
}
 
bool GUISlider::Update()   // 슬라이더 조절 로직
{
    mousePoint = GetMousePosition();
 
    if (CheckCollisionPointRec(mousePoint, leftButton)) arrow = arrowCheck::left;  // 슬라이더의 왼쪽 버튼 체크 상태 확인
     else if (CheckCollisionPointRec(mousePoint, rightButton)) arrow = arrowCheck::right;  // 슬라이더의 오른쪽 버튼 체크 상태 확인
     else if (CheckCollisionPointRec(mousePoint, rect)) arrow = arrowCheck::slider;  // 마우스가 슬라이더를 가리키는지 확인
     else arrow = arrowCheck::none; 
 
    // 슬라이더의 왼쪽 버튼 
    if (arrow == arrowCheck::left && IsMouseButtonPressed(MOUSE_BUTTON_LEFT))
    {
        ValueDown();
        return true;
    }
 
    // 슬라이더의 오른쪽 버튼 
    if (arrow == arrowCheck::right && IsMouseButtonPressed(MOUSE_BUTTON_LEFT))
    {
        ValueUp();
        return true;
    }
 
    // 슬라이더가 선택된 상태에서는  좌우 키 조작이 가능하게 한다
    if (isChecked)
    {
        if (IsLeftAxisLeftChecked()) {ValueDown(); return true; } 
 
        if (IsLeftAxisRightChecked()) {ValueUp(); return true; }
    }
 
    return MouseSliding();
}
 
// 슬라이더의 값을 올리는 함수 
void GUISlider::ValueUp()
{
    *sliderValue += 0.1;
    if (*sliderValue >= 1.0f) *sliderValue = 1.0f;
}
 
// 슬라이더의 값을 내리는 함수
void GUISlider::ValueDown()
{
    *sliderValue -= 0.1;
    if (*sliderValue <= 0.0f) *sliderValue = 0.0f;
}
 
// 슬라이더 그리기
void GUISlider::Draw()
{
    DrawRectangle(x, y, width, height, DARKGREEN);   // 버튼을 그린다. 
 
    // 슬라이더 그리기 
    if (isChecked)
    {
        DrawRectangle(x, y, width, height, Color{255, 255, 255, 85});   // 버튼을 그린다. 
        DrawRectangleLinesEx(rect, 4, Fade(BLACK, 0.3f));         // 버튼 꾸미기
    }else 
    {
        DrawRectangle(x, y, width, height, Color{255, 255, 255, 85});   // 버튼을 그린다. 
    }
 
    // 양옆에 화살표
    DrawRectangleRec(leftButton,  ORANGE );
    DrawRectangleRec(rightButton, ORANGE );
    DrawRectangle(x, y, width *  *(sliderValue), height, RAYWHITE);   // 버튼을 그린다. 슬라이더 값이 포인터형이므로 역참조로 해야 한다. 
 
    // 텍스트 출력
    Vector2 length = MeasureTextEx(*font, title.c_str(), 32, 5); // 글자의 포인트는 32으로 가정할 것이다.  32으로 가정한 글자의 가로 픽셀 크기를 구한다. 
    Vector2 pos = {(float)x  + (width / 2) - (length.x / 2), (float)y + (height /2) - (length.y / 2)};     // 글자를 가로 및 세로 가운데 정렬한다. 
    DrawTextEx(*font, title.c_str(), pos, 32, 5, DARKBROWN);  
 
    // 화살표 선택했는지 여부 
    if (arrow == arrowCheck::left)
    {
        DrawRectangleLinesEx(leftButton, 4, Fade(BLACK, 0.3f));
    }else if (arrow == arrowCheck::right)
    {
        DrawRectangleLinesEx(rightButton, 4, Fade(BLACK, 0.3f));
    }else if (arrow == arrowCheck::slider)
    {
        DrawRectangleLinesEx(rect, 4, Fade(BLACK, 0.3f));
    }
}
 
// 마우스로 슬라이딩
bool GUISlider::MouseSliding()
{
        // 슬라이더의 가운데를 누른 상태 체크 
    if (arrow == arrowCheck::slider && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) changeValueMode = true;  // 마우스로 슬라이더 값을 조절 할 수 있음
 
    // 슬라이더 값을 조절할 수 있는 상태에서
    if (changeValueMode)
    {
        if (GetMousePosition().x >= rect.x + rect.width) SetMousePosition(rect.x + rect.width, rect.y + rect.height / 2);  // 마우스 범위 제한
        if (GetMousePosition().x <= rect.x) SetMousePosition(rect.x, rect.y + rect.height / 2);  // 마우스 범위 제한 
 
        float rawValue = GetMousePosition().x - rect.x;   // 현재 마우스의 위치를 통해 가로 값을 정하기
 
        if (rawValue <= 0) rawValue = 0;    // 슬라이더 가로 값의 좌측 한계 
        if (rawValue >= rect.width) rawValue = rect.width;  // 슬라이더 가로 값의 우측 한계 
 
        float perValue = rawValue / rect.width;  // 슬라이더 가로 값을 퍼센트로 만들기 
        *sliderValue = perValue;  // 슬라이더 값으로 넘겨주기
 
        if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT))   // 마우스 왼쪽 버튼을 떼면 볼륨 설정하고, 변환 모드를 해제함
        {
            changeValueMode = false;
            return true;   // 볼륨이 바뀌면 호출한 쪽에 true를 리턴해 준다
        }
 
        return false;
    }
    return false;
}

가. 개요

기존의 기본 클래스인 button의 기본적인 기능은 상속한다. 이를테면 checked 여부는 호출하는 쪽에서 설정하게 되어 있는데, 이는 button이나 slider나 차이가 없다. 따라서 이러한 것들은 상속하면 된다.

Update()와 Draw()는 둘이 다르므로 virtual로 선언하여 각기 다르게 로직을 실행하게 했다.

나. slider value

slider value는 포인터로 선언하여 대입했다. 호출하는 쪽의 값(예를 들어 볼륨 값)을 호출받는 slider의 slider value가 변하면 같이 변하게 하기 위하여 포인터로 대입했다.

GUISlider::GUISlider(int slider_x, int slider_y, int slider_width, int slider_height, std::string slider_title, Font* _font, float *value)

다. Update() 메서드

(1) 마우스의 위치 확인

마우스가 슬라이더가 가지고 있는 세가지 버튼(슬라이더본체, 좌, 우 버튼) 중 어느 부분을 가리키고 있는지를 판단하기 위해 arrow check라는 enum을 이용한다.

    if (CheckCollisionPointRec(mousePoint, leftButton)) arrow = arrowCheck::left;  // 슬라이더의 왼쪽 버튼 체크 상태 확인
     else if (CheckCollisionPointRec(mousePoint, rightButton)) arrow = arrowCheck::right;  // 슬라이더의 오른쪽 버튼 체크 상태 확인
     else if (CheckCollisionPointRec(mousePoint, rect)) arrow = arrowCheck::slider;  // 마우스가 슬라이더를 가리키는지 확인
     else arrow = arrowCheck::none; 
(2) 슬라이더가 선택된 상태에서 좌우 키로 값을 올리고 낮추기

다음과 같이 checked가 true인 상태에서 update 루프 내에서 코드를 짜주면 된다.

    // 슬라이더가 선택된 상태에서는  좌우 키 조작이 가능하게 한다
    if (isChecked)
    {
        if (IsLeftAxisLeftChecked()) {ValueDown(); return true; } 
 
        if (IsLeftAxisRightChecked()) {ValueUp(); return true; }
    }

라. MouseSliding() 메서드

// 마우스로 슬라이딩
bool GUISlider::MouseSliding()
{
        // 슬라이더의 가운데를 누른 상태 체크 
    if (arrow == arrowCheck::slider && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) changeValueMode = true;  // 마우스로 슬라이더 값을 조절 할 수 있음
 
    // 슬라이더 값을 조절할 수 있는 상태에서
    if (changeValueMode)
    {
        if (GetMousePosition().x >= rect.x + rect.width) SetMousePosition(rect.x + rect.width, rect.y + rect.height / 2);  // 마우스 범위 제한
        if (GetMousePosition().x <= rect.x) SetMousePosition(rect.x, rect.y + rect.height / 2);  // 마우스 범위 제한 
 
        float rawValue = GetMousePosition().x - rect.x;   // 현재 마우스의 위치를 통해 가로 값을 정하기
 
        if (rawValue <= 0) rawValue = 0;    // 슬라이더 가로 값의 좌측 한계 
        if (rawValue >= rect.width) rawValue = rect.width;  // 슬라이더 가로 값의 우측 한계 
 
        float perValue = rawValue / rect.width;  // 슬라이더 가로 값을 퍼센트로 만들기 
        *sliderValue = perValue;  // 슬라이더 값으로 넘겨주기
 
        if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT))   // 마우스 왼쪽 버튼을 떼면 볼륨 설정하고, 변환 모드를 해제함
        {
            changeValueMode = false;
            return true;   // 볼륨이 바뀌면 호출한 쪽에 true를 리턴해 준다
        }
 
        return false;
    }
    return false;
}

우선 현재 슬라이더 arrow check가 slider인 상태에서는 값을 변할 수 있게 해준다.

그리고 마우스가 떼지면(IsMouseBUttonReleased), changeValueMode를 false로 해준다. 이렇게 하면 마우스를 드래그 하는 기능과 똑같을 것이다.

마우스를 누르고 있는 상태에서는 마우스 커서가 슬라이더의 좌우를 넘지 못하도록 짰다.

그리고 현재 x위치에 맡게 slider value를 대입해 준다. 이렇게 하면 마우스를 떼면 slider value가 바뀌는 것이다.

마. Draw() 메서드

딱히 설명할 부분이 없다. slidervalue의 값에 따라 박스의 width가 달라지게 하면 슬라이더가 그려지게 될 것이다.

전체 소스는 다음과 같다.

// 슬라이더 그리기
void GUISlider::Draw()
{
    DrawRectangle(x, y, width, height, DARKGREEN);   // 버튼을 그린다. 
 
    // 슬라이더 그리기 
    if (isChecked)
    {
        DrawRectangle(x, y, width, height, Color{255, 255, 255, 85});   // 버튼을 그린다. 
        DrawRectangleLinesEx(rect, 4, Fade(BLACK, 0.3f));         // 버튼 꾸미기
    }else 
    {
        DrawRectangle(x, y, width, height, Color{255, 255, 255, 85});   // 버튼을 그린다. 
    }
 
    // 양옆에 화살표
    DrawRectangleRec(leftButton,  ORANGE );
    DrawRectangleRec(rightButton, ORANGE );
    DrawRectangle(x, y, width *  *(sliderValue), height, RAYWHITE);   // 버튼을 그린다. 슬라이더 값이 포인터형이므로 역참조로 해야 한다. 
 
    // 텍스트 출력
    Vector2 length = MeasureTextEx(*font, title.c_str(), 32, 5); // 글자의 포인트는 32으로 가정할 것이다.  32으로 가정한 글자의 가로 픽셀 크기를 구한다. 
    Vector2 pos = {(float)x  + (width / 2) - (length.x / 2), (float)y + (height /2) - (length.y / 2)};     // 글자를 가로 및 세로 가운데 정렬한다. 
    DrawTextEx(*font, title.c_str(), pos, 32, 5, DARKBROWN);  
 
    // 화살표 선택했는지 여부 
    if (arrow == arrowCheck::left)
    {
        DrawRectangleLinesEx(leftButton, 4, Fade(BLACK, 0.3f));
    }else if (arrow == arrowCheck::right)
    {
        DrawRectangleLinesEx(rightButton, 4, Fade(BLACK, 0.3f));
    }else if (arrow == arrowCheck::slider)
    {
        DrawRectangleLinesEx(rect, 4, Fade(BLACK, 0.3f));
    }
}

Setting screen에서 호출하기

1. 개요

환경설정 화면을 만들 것인데, 이는 기존에 메뉴를 만들었던것과 대동소이하다.

따라서 이에 대한 설명은 생략한다. 사소하게 바뀐 부분이 있을텐데, 이는 완성된 소스파일을 참조하자.

2. settingscreen.h

settingscreen.h
#pragma once
#include <string>
#include "raylib.h" 
#include "config.h" 
#include "gui.h"
#include "fontutil.h"
#include "buttonActionMap.h"
 
 
class SettingScreen
{
    public :
        void Init(int width, int height, std::string title); 
        void Tick();
        SettingScreen();
        ~SettingScreen();
 
    private: 
        void Draw(); 
        void Update(); 
        void KeyboardInputCheck();
        void KeyboardButtonCheck();
 
        enum class settingMenuEnum 
        {
            volume = 0, save, menu, exit
        };
 
        GUI* gui[4];
 
        Font font;
        Texture2D img[6];
 
        SceneManager* menuManager;
        settingMenuEnum menuState;
 
        Music music;  // 배경음악 선언 
        int gamepad = 0; // 제일 처음에 꽂은 게임패드만 사용하도록 하자. 제일 처음이므로 0이다. 
 
        std::vector<std::string> texts;
 
};

GUI클래스를 포인터로 선언하였다. 그리고 slider 클래스도 부모클래스인 gui 클래스 배열에 같이 넣어줄 수 있음을 주목하자.

2. Init() 함수

settingscreen의 Init() 함수 내에서 각 gui를 생성하는 것을 보자.

guislider는 버튼클래스와 다르게 정의해 줄때는 guislider로 만들어 줘야 하는 것을 알 수 있다.

    gui[0] = new GUISlider(240, 140, 400, 40, texts[4].c_str(), &font, &menuManager->volume);   // 슬라이더 
    gui[1] = new GUI(GetScreenWidth() - 200, GetScreenHeight() - 200, 180, 40, texts[1].c_str(), &font);   // 저장 버튼
    gui[2] = new GUI(GetScreenWidth() - 200, GetScreenHeight() - 140, 180, 40, texts[2].c_str(), &font);  // 메뉴 버튼
    gui[3] = new GUI(GetScreenWidth() - 200, GetScreenHeight() - 80, 180, 40, texts[3].c_str(), &font);  // 종료 버튼

3. update() 함수

settingscreen의 update()함수 내에서 단순하게 루프만 돌리면서 각 gui배열의 update함수를 불러오면 된다.

    // 각 GUI의 로직을 실행한다. 
    for (int i =0; i<4; i++)
    {
        if (gui[i]->Update())   // gui[0]는 guiSlider이고, 나머지는 gui이다. 
        {
            SetMusicVolume(music, menuManager->volume);
        }
    }

gui[0]는 자식 클래스인 guislider이다. 따라서 0번째 gui는 guislider의 update()가 실행된다. 나머지는 버튼 클래스의 update()가 실행된다.

4. keyboardbuttoncheck() 메서드

현재 gui가 선택된 것인지 여부를 판단해주는 것이므로 기본 클래스인 버튼 gui의 메서드가 실행된다.

// 현재 메뉴 상태에 따라 키보드 모양을 달리 정해줌
void SettingScreen::KeyboardButtonCheck()
{
    // gui가 선택된 것인지 확인해 줌
    for (int i =0; i < 4; i++)
    {
        if (i == (int)menuState) 
            gui[i]->Checked();
        else 
            gui[i]->UnChecked();
    }
 
}

5. Draw() 메서드

Draw메서드도 단순하게 gui배열에 대한 루프문을 돌리면 알아서 guislier객체 대해서는 그에 해당하는 draw()메서드가 실행된다.

    // gui 그리기
    for (int i =0; i < 4; i++)
    {
        gui[i]->Draw(); 
    }

결론

1. 라이브러리 업데이트

gui 라이브러리는 계속하여 업데이트 할 예정이다.

Raylib GUI Library

2023. 10. 29.

슬라이더를 확장하여 이미지를 넣은 슬라이더도 만들었다.

Raylib Image Slider

2023. 10. 30.

체크박스 라이브러리도 같이 만들었다.

Raylib Image Slider and CheckBox

2. 소스파일

지금까지의 소스파일이다. 이미지슬라이더와 체크박스를 이용했다.

이에 대한 활용방법은 다음 소스파일을 참고하자.

이미지 슬라이더와 체크박스 GUI만들기

Raylib GUI Library을 읽어보자

3. 다음 강좌

다음에는 JSON으로 파일 저장 및 읽기를 해볼 것이다.

1)
기본 버튼에 해당하는 클래스
로그인하면 댓글을 남길 수 있습니다.

raylib/flappybird/slider_gui_만들기.txt · 마지막으로 수정됨: 2023/11/20 00:10 저자 이거니맨