본문 바로가기
Develop/Win32 API와 게임 엔진

[개발] 공부하며 정리한 "KeyManager"

by Tarra 2023. 10. 20.

 

 


개인 공부 후 자료를 남겨놓기 위한 목적이므로,
생략되거나 오류가 있을 수 있음을 알립니다.

잘못된 부분이 있다면 댓글로 상냥하게 가르쳐주시면 감사하겠습니다!

 

 

Singleton 패턴을 이용하여 KeyManager를 만들어 키입력을 받아보자

 

 

보며 공부한 곳! // 어소트락 아카데미 Win32 API 무료강의

https://youtu.be/dlFr-OnHlWU?si=K8UpK8CwSOddqFZ5


 

파일 구조를 다음과 같이 변경하고, 

 

이제는 여러 매니저들을 만들어볼 차례이다.

 

해당 매니저들의 역할은 Core를 도와 여러 잡일을 한다고 생각하면 된다.

 

예를 들면 키입력, 화면 전환, 카메라 이동, 데이터 관리등을 담당하게 된다.

 

이러한 모든 매니저들 또한 프로그램이 실행되어 종료될 때까지 1개 이상 만들어지거나 중간에 삭제되면 안되기 때문에

 

싱글톤 패턴을 사용하게 된다.

 

 

 

위는 이전에 작성했던 싱글톤 패턴의 핵심 요소이다.

 

위 코드는 모든 매니저에서 공통적으로 사용하게 되므로 이를 매크로를 이용하여 압축시켜보도록 하자.

 

다음과 같이 define.h 파일을 생성한 뒤

 

위와 같이 작성해준다면 언제든지 SINGLE(type) 을 통해 간편하게 싱글톤 패턴을 사용할 수 있게 된다.

 

 

이때 필자는 define.h를 pch.h라는 헤더에 넣었고 이를 전역으로 뿌려주는 작업을 진행하였다.

pch.h가 대표로 뿌려주고, define.h를 비롯한 다양한 헤더들은 pch.h에서 include 하는 형식

 


KeyManager

첫번째 매니저인 KeyManager를 만들어보자.

 

KeyManger는 다음과 같이 작동한다.

 

 

위의 설계를 기반으로 KeyMgr.h를 작성하면 다음과 같이 작성된다.

 

 

이후 init() 함수를 다음과 같이 작성한다.

 

KeyMgr의 update() 멤버함수는 프로그램에 들어오는 입력에 맞춰 신호를 받야하므로,

 

win32 api의 키입력과 enum class KEY를 매핑시켜주기 위해 다음과 같은 배열을 선언한다.

 

KEY와 같은 순서로 초기화된 것을 알 수 있다.

 

이후 update()에서는 함수 호출시마다. 윈도우에 들어오는 키 입력에 맞추어 m_vecKey의 값들을 변화시켜주어야 한다.

코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
void KeyMgr::update()
{
    // 윈도우 포커싱 확인
    HWND hWnd = GetFocus();
 
    // 윈도우가 포커싱되었을때 동작한다.
    if (nullptr != hWnd)
    {
        // 모든 키에 대해
        for (int i = 0; i < (int)KEY::LAST; i++)
        {
            // 해당 키에 대한 메시지가 들어왔다면
            if (GetAsyncKeyState(g_arrVK[i]) & 0x8000)
            {
                // 이전에도 눌려있었을 경우(TAP) -> HOLD로 전환
                if (m_vecKey[i].bPrevPush)
                {
                    m_vecKey[i].eState = KEY_STATE::HOLD;
                }
 
                // 아무 상태도 아니었을 경우
                else
                {
                    m_vecKey[i].eState = KEY_STATE::TAP;
                }
 
                // 이전에 눌렸다는 기록을 남김
                m_vecKey[i].bPrevPush = true;
            }
            // 해당 키에 대한 메시지가 없다면
            else
            {
                // 이전에 해당 키가 눌려있었을 경우
                if (m_vecKey[i].bPrevPush)
                {
                    m_vecKey[i].eState = KEY_STATE::AWAY;
                }
 
                // 이전에 해당키가 눌려있지 않았을 경우
                else
                {
                    m_vecKey[i].eState = KEY_STATE::NONE;
                }
 
                m_vecKey[i].bPrevPush = false;
            }
        }
    }
    
    // 윈도우 포커싱 해제 상태
    else
    {
        // 모든 키를 AWAY -> NONE 순서대로 해제한다.
        for (int i = 0; i < (int)KEY::LAST; ++i)
        {
            m_vecKey[i].bPrevPush = false;
            if (KEY_STATE::TAP == m_vecKey[i].eState || KEY_STATE::HOLD == m_vecKey[i].eState)
            {
                m_vecKey[i].eState = KEY_STATE::AWAY;
            }
            else if (KEY_STATE::AWAY == m_vecKey[i].eState)
            {
                m_vecKey[i].eState = KEY_STATE::NONE;
            }
        }
    }
}
cs

 

이제 기본적인 키 입력에 대한 함수는 작성되었으니,

 

해당 KeyMgr이 잘 작동할 수 있도록 Core로 넘어가 CCore의 init()에 KeyMgr의 init()을 작성해주고,

 

매 루프마다 KeyMgr의 update()가 동작할 수 있도록 progress에도 넣어주자.

 

 

 

 

화면에 그림을 그려 키 입력이 잘 작동하는지 확인해보도록 하자.

 

화면에 그림을 그리기 위해서는 메인 윈도우, 해상도, 그림을 그릴 DC가 필요하다.

이 3가지는 무언가를 표현하는데 있어 빠져서는 안될 필수요소이기 때문에

프로그램이 실행되고 가장 처음 실행되는 Core의 init()에서 변수를 찾아 Core에서 가지고 있어야 한다.

맨처음 init이 호출될 때에 메인 윈도우 정보와 해상도 값을 넘겨준다.


Core가 가지고 있어야 할 멤버 변수들

 

init()시 들어오는 인자에 맞추어 멤버 변수를 초기화한다.

 

 

매번 호출되는 progress에서 키입력이 변경되는대로 화면에 사각형을 움직이도록 해보자.

 

사각형을 움직이기 위한 코드는 다음과 같이 작성했다.

 

 

잘 보이진 않지만 사각형이 잘 움직이고 있는 것은 확인할 수 있다.

 

 

KeyManager의 간단한 구현은 이렇게 마무리되었고,

 

다음 글에서는 사각형이 왜 저렇게 깜빡이며 잘 보이지 않는지와 "더블 버퍼링"에 대해 알아보도록 하자.