본문 바로가기
Win32 API/기초

Win32 API 기초 : Timer (1)

글: 시플마 2024. 6. 27.

키 입력이 생길 때마다 사각형이

1 픽셀 움직이도록 하였더니

컴퓨터의 처리 속도가 너무 빨라서

살짝 키 입력을 주어도 사각형이 너무 많이

움직인다는 문제점이 있었습니다.

 

이를 해결하려면 키 입력이 들어왔을 때

1 픽셀이 아니라 0.01 정도로 해 주면 개선되겠죠.

 

 

이렇게 사각형의 위칫값을 실수로 저장하려면

사각형의 위칫값을 담당하는 멤버 변수의 타입을

실수 타입으로 바꿔 주어야겠죠.

 

기존에는 사각형의 위칫값을 저장하는 멤버 변수는

POINT형, 즉 두 개의 정수로 이루어져 있었습니다.

 

이것을 두 개의 실수로 위칫값을 나타내고자 하므로 

위칫값을 저장하기 위한 새로운 자료형(구조체)를

하나 만들어 보도록 합시다.

 

 

Header 필터 안에 struct.h 파일을 만들고

 

Vec2라는 구조체를 만들었습니다.

 

Vector를 표현해 주는 구조체입니다.

 

여기서 Vector는 가변 배열을 의미하는 것이 아니라

방향과 크기를 동시에 나타내는 Vector를 의미합니다.

 

멤버 변수로 실수형 x 좌푯값과

실수형 y 좌푯값을 갖고 있습니다.

 

8번째 줄에 있는 생성자는 기본 생성자,

13번째 줄에 있는 생성자는 인자로 실수를 받았을 때

호출되는 생성자이며 18번째 줄에 있는 생성자는

인자로 정수를 받았을 때 호출되는 생성자입니다.

 

정수를 인자로 받았을 때는 float으로 형변환하여

멤버 변수를 초기화해 주고 있습니다.

 

 


 

Vec2라는 구조체를 통해 자료형을 하나 만들었으니

 

CObject 클래스에 적용해 봅시다.

 

사각형의 좌푯값을 저장하는 Vec2형 멤버 변수 m_vPos,

사각형의 크기를 저장하는 Vec2형 멤버 변수 m_vScale을 두었습니다.

 

 

멤버 변수를 모두 private로 선언하였기 때문에

외부에서 멤버 변수의 값을 수정할 수 있도록

좌푯값을 수정하는 함수 SetPos와

크기를 수정하는 함수 SetScale를 선언하고 정의하였습니다.

 

 

외부에서 멤버 변수들의 값을 얻을 수 있도록

GetPos 함수와 GetScale 함수도 만들었죠.

 

 

 


 

 

CCore 클래스로 만든 싱글톤 객체가 초기화될 때

 

사각형의 멤버 변수들도 같이 초기화하겠습니다.

 

29번째 줄에서 CObject 클래스로 만든 사각형 객체 g_obj의

멤버 함수 SetPos를 통해 사각형의 좌푯값을 설정하고 있습니다.

 

화면 중앙에 사각형이 위치할 수 있도록 해상도의 가로와 세로 길이를

절반으로 나눈 값을 위칫값으로 설정하고 있습니다.

 

이때 해상도는 POINT 자료형, 즉 long 정수 타입이므로

Vec2 자료형인 실수 타입 멤버 변수에 대입하려면

float으로 형변환을 해 주어야 합니다.

 

 

30번째 줄에서는 사각형의 크기를 설정하고 있습니다.

 

 

 


 

 

 

키 입력에 따른 사각형의 위치 변화를 구현한 

 

update 함수도 수정해 주었습니다.

 

기존 사각형의 위칫값을 지역 변수 vPos에 

저장하였다가 키 입력을 받으면 vPos의 

값을 바꿉니다. 기존에는 값을 1씩 바꾸었지만,

아주 빠르게 작동하는 컴퓨터의 특징을 고려하여

0.01씩 바꾸도록 수정하였습니다.

 

물론 만약 키 입력을 통해 x 좌푯값이 2.5 증가해도

화면에서는 픽셀 단위, 즉 정수 단위로 출력되므로

소수점을 버리게 되어 2 픽셀만큼 이동하게 되겠죠.

 

이후 바뀐 vPos의 값을 다시

SetPos 함수를 통해 사각형에 대입합니다.

 

 

 


 

 

 

 

사각형을 화면에 그리는 render 함수에서

 

사각형의 위칫값을 저장하는 vPos와

사각형의 크기를 저장하는 vScale을 선언하고

 

이 지역 변수들을 통해 사각형을 그리고 있습니다.

 

Vec2 자료형은 모두 실수로 이루어져 있으므로 

나누기 연산을 할 때에도 실수로 해 주어야 하기 때문에 

2 뒤에 float을 의미하는 f를 붙였습니다.

 

근데 Rectangle 함수가 인자로 정수를 받기 때문에

연산 결과를 다시 int로 형변환하여 넘겨주는 모습입니다.

 

 

이후 프로그램을 실행하고 키 입력을 해 보니

 

확실히 이전보다는 미세하게

사각형을 움직일 수 있는 모습입니다.

 

 

 


 

 

 

 

아직 해결하지 못한 문제가 있습니다.

 

개인 컴퓨터의 성능은 모두 다릅니다.

 

어떤 컴퓨터는 빠르고 어떤 컴퓨터는 상대적으로 느릴 수 있죠.

 

 

위에서 작성한 사각형이 움직이는 코드는 큰 문제가 있습니다.

 

바로 컴퓨터 성능에 따라 움직이는 거리가 다를 것입니다.

 

 

게임에 비유하자면 1초당 200 Frame을 실행하는 컴퓨터 A와

1초당 100 Frame을 실행하는 컴퓨터 B가 있습니다.

 

캐릭터를 움직이는데 똑같은 시간 동안

키를 입력하고 있어도 성능이 더 좋은 A 컴퓨터가

더 많은 거리를 이동하게 될 것입니다.

 

상당히 큰 문제이죠.

 

컴퓨터 A가 1 Frame을 실행할 때 걸리는 시간은

1 / 200 = 0.005초입니다.

 

컴퓨터 B가 1 Frame을 실행할 때 걸리는 시간은

1 / 100 = 0.01초입니다.

 

시간 차이가 두 배가 나기 때문에 1초 동안 컴퓨터 A가

두 배 더 많은 거리를 이동할 것입니다.

 

 

이를 해결하려면 키 입력이 발생했을 때

이동하는 거리에 Delta Time을 곱하면 됩니다.

 

Delta Time이란 1 Frame을 실행하는데 걸리는 시간값입니다.

 

만약 키 입력을 했을 때 사각형을 2 픽셀만큼 움직이게 하고자

할 때 컴퓨터 A는 2에 0.005를 곱해 0.01만큼 이동하면 됩니다.

 

컴퓨터 B는 2에 0.01을 곱해 0.02만큼 이동하면 됩니다.

 

이런 식으로 성능이 두 배 좋은 컴퓨터 A가 컴퓨터 B보다

덜 이동하게 함으로써 균형을 이루게 만들 수 있는 것이죠.

 

 

이러한 동작을 위해서는 프로그램에서 시간을 관리하는

매니저가 필요할 거 같습니다. 그래서 Manager 필터 안에

TimeMgr 필터를 만들고 CTimeMgr이라는 클래스를 만듭니다.

 

시간 매니저는 하나만 존재해야 하므로

SINGLE 매크로 함수를 통해 객체를 생성합니다.

 

 

멤버 두 개와 초기화 함수인 init 함수를 하나 만들었습니다.

 

init 함수의 정의를 봅시다.

 

시간을 얻을 수 있는 함수는 GetTickCount가 있었죠?

해당 함수는 1초에 1000을 셀 수 있는 함수입니다.

 

그러나 progress 함수가 1초에 호출되는 횟수는 

3만 번 이상이었습니다. 1초에 3만 Frame 이상을

체크해야 하는데 1초에 최대 1000까지만 체크할 수 있는

GetTickCount 함수는 적절하지 않아 보이네요.

 

더 빠른 함수가 존재하는데

14번째 줄에 있는 QueryPerformanceCounter 함수입니다.

 

해당 함수로 체크한 시간은 LARGE_INTEGER라는

자료형으로 반환이 됩니다. 이를 멤버 변수 m_liCurCount의

주솟값을 넣어서 대입해 줄겁니다. 

 

 

또한 GetTickCount 함수는 1초에 1000을 센다는 기준이 

있었지만, QueryPerformance는 기준이 따로 없기 때문에 

QueryPerformanceFrequency라는 함수를 통해

직접 값을 얻어야 합니다.

 

이 값은 QueryPerformanceFrequency 함수에

인자로 넣은 멤버 변수 m_liFrequency의 주솟값을 통해

m_liFrequency에 대입해 줍니다.

 

 

 

 


 

 

 

CTimeMgr 클래스를 통해 만든 매니저 객체는 

 

CCore 클래스를 통해 만든 코어 객체의

초기화 함수가 실행될 때 같이 초기화해 주겠습니다. 

 

31번째 줄에서 GetInstance 함수를 통해 data 영역에

싱글톤 객체를 만들고 init 함수를 통해 초기화를 진행하는 모습입니다.

 

 

32번째 줄에서는 CKeyMgr 클래스를 통해

키 매니저 객체를 만들고 초기화해 주는 모습이죠.

(CKeyMgr 클래스는 틀만 만들었을 뿐, 아직 구현된 것이 없습니다.)

 

 

CTimeMgr 클래스의 초기화 함수인 init 함수가 실행되었으니 

QueryPerformance가 1초에 얼마나 카운트하는지 확인할 수 있겠네요.

 

CTimeMgr 클래스의 init 함수가 정의된 부분에서

18번째 줄에 중단점을 찍고 프로그램을 실행하겠습니다.

 

그리고 얼마나 카운트하는지 알 수 있도록

멤버 변수 m_liFrequency에 값을 저장했었죠?

 

10,000,000이라는 값이 저장되었습니다. 

 

이는 초당 천 만 카운트를 셀 수 있다는 의미이므로

초당 3만 번 이상의 Frame을 체크하는데 문제가 없겠네요.

 

 

 

 


강의 출처 : https://www.youtube.com/watch?v=RDv3t2Lki9I