(2022) 공부 (Study)/게임 개발 (Development)

[Win32API] GetAsyncKeyState() 와 키 입력 받기 (2)

수낭 2022. 4. 14. 13:06

https://soonang2.tistory.com/28

 

[Win32API] GetAsyncKeyState() 와 키 입력 받기 (1)

이번에는 가상 키 값과 키보드 입력을 받는 방법에 대해서 알아보겠습니다. _getch() 함수는 가장 마지막에 입력한 값만 받을 수 있으므로, 대각선 이동을 구현할 수 없습니다. switch(wParam) / case WM_

soonang2.tistory.com

이전 포스팅을 보고 오시는 것을 추천드립니다.

 

이번 포스팅에 해당되는 코드는 아래 git에서 확인 가능하다.

https://github.com/nr97819/Win32_ASTR_Lecture/tree/Lecture-12

(강의 13으로 교체 필요)

 

void CCore::update()
{
	Vec2 vPos = g_obj.GetPos();

	if (GetAsyncKeyState(VK_LEFT) & 0x8000)
	{
		vPos.x -= 300.f * CTimeMgr::GetInst()->GetfDT();
	}
	if (GetAsyncKeyState(VK_RIGHT) & 0x8000)
	{
		vPos.x += 300.f * CTimeMgr::GetInst()->GetfDT();
	}
	if (GetAsyncKeyState(VK_UP) & 0x8000)
	{
		vPos.y -= 300.f * CTimeMgr::GetInst()->GetfDT();
	}
	if (GetAsyncKeyState(VK_DOWN) & 0x8000)
	{
		vPos.y += 300.f * CTimeMgr::GetInst()->GetfDT();
	}

	g_obj.SetPos(vPos);
}

기존의 update() 함수는 위와 같았다.
이것을 이제 KeyManager 클래스로 별도로 빼서 수정할 것이다.

 

< KeyManager 클래스가 별도로 필요한 이유 >


(1) 프레임 동기화

동일 프레임 내에서 같은 키에 대해 동일한 이벤트를 가져간다.


(2) 키 입력의 다양한 State 처리

키 눌림 State에 대한 상세한 처리(Tap, Hold, Away) 방법이 Window에는 없어서, 직접 구현해줘야 한다.
(예를 들어, 키가 안눌린 상태와 이미 눌린 키를 떼는 상태는 각기 다른 이벤트가 적용되어야 한다.)

 

각 개체들의 동작에 대한 처리는 모두가 동일하게 적용받아야 하므로
즉, 일괄적으로 각 개체들의 변경된 상태에 대한 내용들을 Update() 시에 체크해서 fix해 놓고,
이후에 Render()에서 한번에 일괄적으로 동작을 적용 및 반영시켜야 한다.

즉, 매 프레임의 프레임이 시작하는 초입 부분에서 KeyManger 클래스를 거쳐서 1차적으로 해당 Key가 눌렸는지,
혹은 눌린 State를 검사하고, 이후에 각 개체들의 변경된 상태들을 Update() 함수에서 기록한 뒤,
최종적으로 Render() 함수에서 변경된 점들을 일괄적으로 동작을 적용시키는 순서가 된다.

 

KeyManager에서 해당 Key 상태 검사

→ 모두에게 변경 사항 적용 Update( )

→ 최종 확정 동작을 일괄 Render( )

 

main.cpp

...
MSG msg;
while (true)
{
    if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
    {
        if (msg.message == WM_QUIT)
            break;

        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    else
    {
        CCore::GetInst()->progress();
    }
}
return (int) msg.wParam;
...

 

CCore.cpp

...
void CCore::progress()
{
	// Manager Update
	CTimeMgr::GetInst()->Update();
	CKeyMgr::GetInst()->Update(); // KeyMgr 역시 매 프레임마다 update하며, 키 눌림 체크

	update();
	render();
}

void CCore::update()
{
	Vec2 vPos = g_obj.GetPos();

	if (CKeyMgr::GetInst()->GetKeyState(KEY::LEFT) == KEY_STATE::TAP)
	{
		vPos.x -= 300.f;
	}
	/*if (GetAsyncKeyState(VK_LEFT) & 0x8000)
	{
		vPos.x -= 300.f * CTimeMgr::GetInst()->GetfDT();
	}*/
	if (GetAsyncKeyState(VK_RIGHT) & 0x8000)
	{
		vPos.x += 300.f * CTimeMgr::GetInst()->GetfDT();
	}
	if (GetAsyncKeyState(VK_UP) & 0x8000)
	{
		vPos.y -= 300.f * CTimeMgr::GetInst()->GetfDT();
	}
	if (GetAsyncKeyState(VK_DOWN) & 0x8000)
	{
		vPos.y += 300.f * CTimeMgr::GetInst()->GetfDT();
	}

	g_obj.SetPos(vPos);
}
...

 

CKeyMgr.h

#pragma once

// 가상키 값이 아닌, 내가 자체적으로 정의
enum class KEY
{
	LEFT, 
	RIGHT, 
	UP, 
	DOWN,
	Q, W, E, R, T,
	Y, U, I, O, P,
	A, S, D, F, G,
	Z, X, C, V, B,
	ALT, 
	CTRL, 
	LSHIFT, 
	SPACE, 
	ENTER, 
	ESC,

	LAST // 반복문(for)에서 쉽게 LAST 원소를 지정하기 위함
};

enum class KEY_STATE
{
	NONE,	// 계속 안눌린 있는 상태
	TAP,	// 막 누른 시점
	HOLD,	// 계속 눌린 상태
	AWAY,	// 막 뗀 시점
};

struct tKeyInfo
{
	KEY_STATE	estate;
	bool		ePrevPush; // 이전 프레임에 눌린 상태였는지
};

class CKeyMgr
{
	SINGLE(CKeyMgr);
private:
	// vector 내의 index가 곧, enum class KEY의 값이다.
	vector<tKeyInfo> m_vecKey;

public:
	void Init();
	void Update();
	KEY_STATE GetKeyState(KEY _eKey) { return m_vecKey[(int)_eKey].estate; }
};

 

CKeyMgr.cpp

#include "pch.h"
#include "CKeyMgr.h"

// 반드시 cpp 파일에 전역으로 생성해야 하는 것 주의
int g_arrVK[(int)KEY::LAST] =
{
	VK_LEFT,
	VK_RIGHT,
	VK_UP,
	VK_DOWN,
	'Q', 'W', 'E', 'R', 'T',
	'Y', 'U', 'I', 'O', 'P',
	'A', 'S', 'D', 'F', 'G',
	'Z', 'X', 'C', 'V', 'B',
	VK_MENU, // ALT 키
	VK_CONTROL,
	VK_LSHIFT,
	VK_SPACE,
	VK_RETURN,
	VK_ESCAPE
};

CKeyMgr::CKeyMgr()
{

}

CKeyMgr::~CKeyMgr()
{

}

void CKeyMgr::Init()
{
	// Key 값/상태 vector 초기화
	for (int i = 0; i < (int)KEY::LAST; ++i)
	{
		// 처음에는 모두 {None, 눌린적 없음} 으로 초기화
		m_vecKey.push_back(tKeyInfo{ KEY_STATE::NONE, false });
	}
}

void CKeyMgr::Update()
{
	for (int i = 0; i < (int)KEY::LAST; ++i)
	{
		// 해당 키가 [지금] 눌려있다면
		if (GetAsyncKeyState(g_arrVK[i]) & 0x8000) // 비트 연산으로도 가능
		{
			if (m_vecKey[i].ePrevPush)	
			{	// prev:O, now:O
				m_vecKey[i].estate = KEY_STATE::HOLD;
			}
			else						
			{	// prev:X, now:O
				m_vecKey[i].estate = KEY_STATE::TAP;
			}

			m_vecKey[i].ePrevPush = true;
		}
		// 해당 키가 [지금] 안눌려있다면
		else
		{
			if (m_vecKey[i].ePrevPush)	
			{	// prev:O, now:X
				m_vecKey[i].estate = KEY_STATE::AWAY;
			}
			else						
			{	// prev:X, now:X
				m_vecKey[i].estate = KEY_STATE::NONE;
			}

			m_vecKey[i].ePrevPush = false;
		}
	}
}

 

 

https://soonang2.tistory.com/39

 

[Win32API] GetAsyncKeyState() 와 키 입력 받기 (3)

https://soonang2.tistory.com/30 [Win32API] GetAsyncKeyState() 와 키 입력 받기 (2) https://soonang2.tistory.com/28 [Win32API] GetAsyncKeyState() 와 키 입력 받기 (1) 이번에는 가상 키 값과 키보드 입력..

soonang2.tistory.com

위 포스팅에서 Key 입력 3번째 시리즈로 이어집니다.