사용자 도구

사이트 도구


raylib:flappybird:게임패드_입력
게임패드 입력

플래피버드 게임패드 입력

이번에는 게임패드 입력을 해볼 것이다.

스타팅 프로젝트

게임 클래스 수정해 보기

1. 기본 개념

게임패드는 여러개가 있을 수 있다. 통상 게임기들은 게임패드를 4개까지 지원한다. raylib역시 4개 까지의 게임패드를 지원한다.

bool IsGamepadAvailable(int gamepad);                   // Check if a gamepad is available

함수는 몇번째 게임패드가 지금 입력가능한지를 인자로 받아서 리턴해주는 함수이다. 우리는 1인용 게임을 만들므로 제일 첫번쨰, 즉, 0번째 게임패드를 쓸 것이다.

따라서 game.h에는 gamepad라는 멤버 변수를 0으로 정의해 주면 된다.

       int gamepad = 0; // 제일 처음에 꽂은 게임패드만 사용하도록 하자. 제일 처음이므로 0이다. 

2. 게임패드의 버튼

raylib에서 게임패드의 버튼을 enum화 한 것은 Raylib 게임패드 매핑에서 볼 수 있다.

이를 살펴보면, 게임패드 A키는 GAMEPAD_BUTTON_RIGHT_FACE_DOWN이고,

가운데에 있는 select키는 GAMEPAD_BUTTON_MIDDLE_LEFT인 것을 확인할 수 있다. 이를통해 키보드 입력과 동일하게 입력을 받으면 된다.

3. 게임패드 입력 로직

키보드의 IsKeyPressed()에 대응하는 키패드 입력 함수는 IsGamepadButtonPressed()이다1).

다만 키보드는 딱 한개만 상정하므로 바로 키 버튼을 인자로 받는데 반하여 키패드는 여러개가 있을 수 있으므로 어떤 게임패드를 받을 수 있을지를 인자로 받는다.

즉,

IsGamepadButtonPressed(gamepad, GAMEPAD_BUTTON_RIGHT_FACE_DOWN)

와 같이 2개를 인자로 받는 것이다. 이 점만 생각하면 별 어려움이 없다. 게임패드는 수시로 연결이 끊길 수 있으므로 게임패드가 연결되었는지를 확인하고, 연결되지 않았으면 키보드 입력을 받게 로직을 짜보자.

가. 게임화면 로직

Update()메서드에서 다음과 같이 짜면 된다.

        case Gamestate::game :  // 게임로직
 
            // GamePad Logic 
            if (IsGamepadAvailable(gamepad))
            {
                if (IsGamepadButtonPressed(gamepad, GAMEPAD_BUTTON_RIGHT_FACE_DOWN)) bird.Jump();
 
                if (IsGamepadButtonPressed(gamepad, GAMEPAD_BUTTON_MIDDLE_LEFT)) 
                {
                    gamestate = Gamestate::pause;  // 게임정지로 바꾸기 
                    PauseMusicStream(music);
                }
            }else 
            {
               if (IsKeyPressed(KEY_UP)) bird.Jump();   // 새 점프 
 
                // 키보드 입력
                if (IsKeyPressed(KEY_ESCAPE)) 
                {
                    gamestate = Gamestate::pause;  // 게임정지로 바꾸기 
                    PauseMusicStream(music);
                }
            }

나. 정지 화면에서 키 입력

우리는 정지화면에서도 키 입력을 받아야 하므로 다음과 같이 짜게 된다.

        case Gamestate::pause :    // 정지로직  
            // GamePad Logic 
            if (IsGamepadAvailable(gamepad))
            {
                if (IsGamepadButtonPressed(gamepad, GAMEPAD_BUTTON_RIGHT_FACE_DOWN))
                {
                    gamestate = Gamestate::game;    // 게임으로 돌아가기 
                    ResumeMusicStream(music);
                } 
                if (gamestate == Gamestate::pause && IsGamepadButtonPressed(gamepad, GAMEPAD_BUTTON_MIDDLE_LEFT))  Manager->gameExit = true;   // 정지상태일때, ESC 누르면 종료
            }else 
            {
                // 키보드 입력
                if (IsKeyPressed(KEY_UP)) 
                {
                    gamestate = Gamestate::game;    // 게임으로 돌아가기 
                    ResumeMusicStream(music);
                }
                if (gamestate == Gamestate::pause && IsKeyPressed(KEY_ESCAPE) ) Manager->gameExit = true;   // 정지상태일때, ESC 누르면 종료
            }
            break;

4. Draw()메서드

게임패드가 있을 와 없을 때에 따라 화면이 달라지게 로직을 짜자, 게임패드가 있을 때에는 현재 어떤 종류의 게임패드를 가지고 있는지를 화면에 출력하게 하는 것이다.

            DrawBackground(); // 배경그리기 
 
            // 게임 UI  
            if (IsGamepadAvailable(gamepad)) 
            {
                DrawText("Pause : Press ESC", GetScreenWidth() - 200, 20, 20, DARKGRAY);
                DrawText(TextFormat("Pipes : %d", pipes.size()), GetScreenWidth() - 200, 40, 20, DARKGRAY);
                DrawText(TextFormat("Score : %d", bird.Score), GetScreenWidth() - 200, 60, 20, DARKGRAY);
                DrawText(TextFormat("Lifes : %d", bird.Life), GetScreenWidth() - 200, 80, 20, DARKGRAY);
                DrawText(TextFormat("GP%d: %s", gamepad, GetGamepadName(gamepad)), GetScreenWidth() - 200, 100, 20, BLACK);
 
            }else
            {
                DrawText("Pause : Select button", GetScreenWidth() - 200, 20, 20, DARKGRAY);
                DrawText(TextFormat("Pipes : %d", pipes.size()), GetScreenWidth() - 200, 40, 20, DARKGRAY);
                DrawText(TextFormat("Score : %d", bird.Score), GetScreenWidth() - 200, 60, 20, DARKGRAY);
                DrawText(TextFormat("Lifes : %d", bird.Life), GetScreenWidth() - 200, 80, 20, DARKGRAY);
            };
            break;

지금까지는 딱히 어려움은 없어 보인다.

버튼을 추상화하여 매핑하기

1. 문제점

위와 같이 게임패드가 있을 때와 없을 때의 로직을 짜면 계속하여 같은 코드를 반복하게 된다.

플래피 버드처럼 버튼 한개로 조종할 때에야 위 처럼 반복된 코드를 써도 상관없겠지만 사용하는 버튼이 많아지면 짜증이 나게 된다.

즉, 인벤토리버튼, 미니맵버튼, 공격버튼, 점프버튼, 그리고 각종 방향키 버튼에 대해 각각 상응하는 로직들이 두번씩 들어가게 되는 것이다.

따라서 유니티나 언리얼처럼 각각의 액션에 대응하는 키보드와 게임패드를 매핑해주는 작업이 필요하다.

2. 버튼 액션에 대한 매핑

buttonActioinMap.cpp를 다음과 같이 만들었다2).

buttonActionMap.cpp
#include "buttonActionMap.h"
 
// 점프 버튼
bool IsButtonJumpPressed()
{
    for (int i = 0; i < 4; i++)
    {
        if (IsGamepadAvailable(i))
        return IsGamepadButtonPressed(i, GAMEPAD_BUTTON_RIGHT_FACE_DOWN);
    }
 
    return IsKeyPressed(KEY_SPACE);
}
 
// ESC 버튼
bool IsButtonESCPressed()
{
    for (int i=0;i < 4; i++)
    {
        if (IsGamepadAvailable(i))
        return IsGamepadButtonPressed(i, GAMEPAD_BUTTON_MIDDLE_LEFT);
    }
 
    return IsKeyPressed(KEY_ESCAPE);    
}

기존에는 gamepad라는 변수를 0으로 설정하였는데, 이번에는 그냥 for문을 돌려서 4개의 게임패드를 모두 입력 받도록 하였다. 이렇게 하면 연결된 모든 게임패드에서 입력을 줄 수 있을 것이다. =)

3. game.cpp에서의 호출

다음과 같이 Update() 메서드를 간단하게 만들어 줄 수 있다.

        case Gamestate::pause :    // 정지로직  
            if(IsButtonJumpPressed())
            {
                gamestate = Gamestate::game;    // 게임으로 돌아가기 
                ResumeMusicStream(music);
            }
 
            if (gamestate == Gamestate::pause && IsButtonESCPressed() ) Manager->gameExit = true;   // 정지상태일때, ESC 누르면 종료
 
            break;
 
        case Gamestate::game :  // 게임로직
 
            if (IsButtonJumpPressed()) bird.Jump();  // 플레이어 점프
 
            if (IsButtonESCPressed()) 
            {
                gamestate = Gamestate::pause;  // 게임정지로 바꾸기 
                PauseMusicStream(music);
            }

메뉴 화면 버튼 매핑하기

1. 문제점

조이스틱은 Pressed라는 개념이 없다. 키보드로 따지면, IsKeyDown()과 같이 연속됨 스트림으로 입력을 받는다. 따라서 조이스틱으로 메뉴를 선택하게 하려면 한가지 트릭이 필요하다.

조이스틱은 -1부터 1까지의 float 값을 가지고 가운데가 0인데, 그렇다면 0을 문지방으로 만들고 0을 넘는 순간 스위치가 닫히게 해야 한다. 그리고 다시 0이 되어야지만 입력을 받을 수 있게 해야 한다3)

즉, 조이스틱 값이 0일 때에만 입력이 가능하도록 만들어 주면 된다.

2. 해결책

다음과 같이 하면 조이스틱을 위로 눌렀을 때에 버튼을 한번 누른셈이 되고, 아래로 눌렀을 때에 버튼을 한번 누른셈이 된다.

    // GamePad Logic 
    if (IsGamepadAvailable(gamepad))
    {
        float YAxisValue = GetGamepadAxisMovement(gamepad, GAMEPAD_AXIS_LEFT_Y); 
 
        if (YAxisValue < 0 && axisValue == false) 
        {
            axisValue = true; 
            int mI = (int)menuState;  
            mI--; 
            if (mI < 0) mI = 2; 
            menuState = menuEnum(mI); 
 
            KeyboardButtonCheck();
        } 
 
        if (YAxisValue == 0) axisValue = false;
 
 
        if (YAxisValue > 0 && axisValue == false) 
        {
            axisValue = true; 
            int mI = (int)menuState;  
            mI++; 
            if (mI >=  3) mI = 0; 
            menuState = menuEnum(mI); 
 
            KeyboardButtonCheck();
        }
 
    }

3. 조이스틱 매핑하기

위와 같이 기본 로직을 알았으므로 이번에는 이러한 조이스틱을 키보드에 대응시켜서 매핑해보자 buttonActioinMap.cpp에 다음 코드를 추가하자

bool IsLeftAxisUpChecked() // 윗방향 스틱 체크 
{
    for (int i =0; i < 4; i++)
    {
        if (IsGamepadAvailable(i))
        {
            float YAxisValue = GetGamepadAxisMovement(i, GAMEPAD_AXIS_LEFT_Y); 
 
            if (YAxisValue < -0.17 && !AxisValue)  // 조이스틱 축이 딱 0이 안될 가능성도 있으므로 임의의 숫자보다 큰 값을 주게 하였음
            {   
                AxisValue = true;
                return true;
            }else 
            {
                if (YAxisValue >= -0.17 && YAxisValue <= 0.17) AxisValue = false;
            }
            return false;
        }
    }
 
    return IsKeyPressed(KEY_UP);
 
}
 
bool IsLeftAxisDownChecked()  // 아래방향 스틱 체크. 왼쪽 스틱
{
    for (int i = 0; i < 4; i++)
    {
        if (IsGamepadAvailable(0))
        {
            float YAxisValue = GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_Y); 
 
            if (YAxisValue > 0.17 && !AxisValue)  // 조이스틱 축이 딱 0이 안될 가능성도 있으므로 임의의 숫자보다 큰 값을 주게 하였음
            {   
                AxisValue = true;
                return true;
            }else 
            {
                if (YAxisValue >= -0.17 && YAxisValue <= 0.17) AxisValue = false;
            }
 
            return false;
 
        }
    }
    return IsKeyPressed(KEY_DOWN);
}
 
bool IsLeftAxisRightChecked() // 오른쪽 방향 스틱체크. 왼쪽 스틱
{
    for (int i = 0; i < 4; i++)
    {
        if (IsGamepadAvailable(0))
        {
            float XAxisValue = GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_X); 
 
            if (XAxisValue > 0.17 && !AxisValue)  // 조이스틱 축이 딱 0이 안될 가능성도 있으므로 임의의 숫자보다 큰 값을 주게 하였음
            {   
                AxisValue = true;
                return true;
            }else 
            {
                if (XAxisValue >= -0.17 && XAxisValue <= 0.17) AxisValue = false;
            }
 
            return false;
 
        }
    }
    return IsKeyPressed(KEY_RIGHT);
}
 
bool IsLeftAxisLeftChecked() // 왼쪽 방향 스틱체크. 왼쪽 스틱
{
    for (int i = 0; i < 4; i++)
    {
        if (IsGamepadAvailable(0))
        {
            float XAxisValue = GetGamepadAxisMovement(0, GAMEPAD_AXIS_LEFT_X); 
 
            if (XAxisValue < -0.17 && !AxisValue)  // 조이스틱 축이 딱 0이 안될 가능성도 있으므로 임의의 숫자보다 큰 값을 주게 하였음
            {   
                AxisValue = true;
                return true;
            }else 
            {
                if (XAxisValue >= -0.17 && XAxisValue <= 0.17) AxisValue = false;
            }
 
            return false;
 
        }
    }
    return IsKeyPressed(KEY_LEFT);
}

4. 호출하기

위와 같이 매핑을 했으면 다음과 같이 menu.cpp에서의 코드는 간단해진다.

    if (IsLeftAxisUpChecked() )
    {
        int mI = (int)menuState;  
        mI--; 
        if (mI < 0) mI = 2; 
        menuState = menuEnum(mI); 
 
        KeyboardButtonCheck();
    }
 
    if (IsLeftAxisDownChecked())
    {
        int mI = (int)menuState;  
        mI++; 
        if (mI >=  3) mI = 0; 
        menuState = menuEnum(mI); 
 
        KeyboardButtonCheck();
    }

결론

게임패드 입력에 대하여 알아보았다. 게임패드로 게임을 하니까 뭔가 훨씬 더 재미있다.

플래피버드 게임패드 입력

지금까지의 소스파일은

플래피버드 게임패드 입력이다.

참고로, 버튼매핑을 보다 일반화 시킨 라이브러리를 만들었다.

레이라이브 버튼 액션 매핑 라이브러리

다음에는 Slider GUI만들기를 해보자. 환경설정 화면에서 볼륨을 조절하는 것을 만들어 보는 것이다.

1)
버튼을 한번씩만 누르게 하기 위하여 pressed함수를 썼다. 만약에 누른 상태로 계속하여 입력을 받으려면 뒤에 접시마가 'Down'이 될 것이다
2)
물론 함수를 선언해 주어야 하므로 헤더파일은 각자가 알아서 만들자. 헤더파일에는 raylib.h를 선언해주는 것을 잊지 말자
3)
그런데 조이스틱이 고장이 나거나 싸구려 조이스틱이라면 정확히 0이 안될 수도 있으므로 문지방(Threshhold)은 0근처의 임의의 범위를 정해주는게 좋다.
로그인하면 댓글을 남길 수 있습니다.

raylib/flappybird/게임패드_입력.txt · 마지막으로 수정됨: 2023/11/20 00:10 저자 이거니맨