-
실시간 게임(키보드 입력, 프레임레이트)@ 16. 1 ~ 17. 1/게임프로그래밍의 정석 2014. 7. 24. 22:32
한장 한장의 단순한 정지에서 움직임을 메인루프를 빠르게 돌리면 된다..
1초에 메인 루프가 몇 번 도는지를 프레임 레이트라고 부른다.
콘솔에서는 cin으로 입력을 받으면 이미지가 정지된 이유는 입력이 들어올때까지 기다리던 구조
그래서 이 부분을 살펴봐서 있으면 가져오고 없으면 그대로 함수를 종료하는 식으로 바꾸면 된다.
그런데 1초에 몇번이나 루프를 도는지는 컴퓨터 성능과 코드 완성도에 서로 다르다.
어떤건 1000번 어떤건 100번 그러면 어떤건 광속으로 움직이고 어떤건 천천히 움직이고..
프레임 레이트를 내려야할까?
그랬다간 에니메이션 움직임이 어색해질 수도 있다.
그러면 몇 프레임에 한번만 키 입력을 받을까?
이것도 곤란하다 빠르게 키를 입력하면 무시되니까...
해결책은 3가지 방법이 있다.
1. 누른 시간에 비례해서 움직인다.
2. 이전 프레임에서 눌리지 않았을 때만 반응하다.
3. 일정 시간 이상 눌려야 반응한다.
첫번째 방법은 액션게임에서 일반적으로 사용되는 방법인데 한 칸 단위가 아니라 정밀하게 움직이는 게임에서 사용한다.(주로 이동할때...1프레임에 0.2이동 이런식)
두번째 방법은 이전 프레임 상태를 저장해 두고 이번 프레임에서 처음 눌렀을때만 반응..
코드는 이런식이다.
bool newInput = //'a'가 눌려졌다면 true를 반환 아니라면 false
if((!oldinput) && newInput)
{
//반응
}
oldinput=newInput;
위에 조작을 설명하자면..처음에 newinput에 a가 눌려져서 true가 들어가면 최초에 oldinput은 false이므로 반응이 되고
그 이후에 oldinput은 true가 되고 한프레임이 지나간다.(이게 중요 지나감)
다시 처음으로와서 newinput이 안눌렸다면 한프레임이 또 지나가서 oldinput이 false되어서
다음 프레임때 키를 통한 반응이 됨..만약 계속 a를 누른다면 반응이 없지.....
여기서 oldinput이 다음 프레임까지 유지되어야 한다.(뭐 클래스 멤버변수 정도면 되나..)
퍼즐이나 격투 게임에서 주먹을 날리는 경우가 좋다.
세번째 방법은 잠시 누르고 있으면 계속 반응한 채로 두고 싶을때 사용
키를 눌렀을때 바로 반응하면 곤란한 경우나(한번누르고 있는데 aaaaaaaaa 수백번 나오는 상황) 잠시 누르고 있으면 계속 반응한 채로 두고 싶을때(천천히..a...a...a 나오는거?)
a, b를 동시에 눌러야 반응하는 조작이라면 사람이 정말로 동시에 키를 누르기 힘드니까
a를 눌렀을 때 바로 반응하지 않고 몇 프레임이 유지하다가 그 사이에
b가 눌리면 ab가 동시에 입력된 것으로 한다. b를 누르지 않으면 a만 눌린것으로 함..
단, 첫번째 세번째방법은 프레임에 따라 어느정도에 반응할지 결정해야 한다..
성능차이로 프레임 레이트가 변하면 조작감도 변한다.
애초에 프레임 레이트를 알수없다면 액션 게임따윈 절대 만들 수가 없다.
속도가 달라지면 완전히 다른 게임이 되기 때문이다.
프레임 레이트 계산
아까 위에서 이야기 했지만 프레임 레이트란 1초에 루프가 몇번 돌아오는지 나타내는 것 즉, 숫자다.
하지만 이를 직접 구하기는 조금 귀찮으니..거꾸로 루프가 1회당 몇 초가 걸리는지 생각해보자.
그 역수가 결국은 프레임 레이트다.
(여기서 역수란 어떤 수와 곱해서 1이 되게 하는 수)
루프 1회당 걸리는 시간을 측정하는건 매 프레임 루프가 시작될때 시계를 보고
이전 프레임에서 측정한 시각에서 어느정도 지났는지를 보면된다.
나머지는 그 역수를 취하면 프레임 레이트를 얻을 수 있다.
여기서 사용하는 라이브러리는 시각을 밀리초 단위로 반환해준다. (1/1000)
unsigned int cTime = time();
unsigned int frametime = cTime - Ptime; //이전, 현재 소요된 시간, 즉 루프 1회에 몇초인가
Ptime = cTime; //다시 루프1회 시간을 구해야해서 현재시간을 대입unsigned framerate = 1000 / frametime; //몇초에 대한 역수
그러나 윈도우상에서는 다른 프로그램도 함꼐 동작하니까..프레임레이트를 매번 측정해서 표시하면
변동이 심하다. 그래서 평균값을 보통 출력하는데..
unsigned int cTime = time();
unsigned int frametime = cTime - Ptime[0]; //지금 측정한 시각에서 제일 오래된 기록을 뺀다. (최근 10프레임에 걸린시간 확인)for(int i = 0; i < 9; ++i)
{
Ptime[i] = Ptime[i + 1]; //한칸씩 값을 앞으로 옮김 이 절차가 다 진행되야 10프레임이 지난다..한칸씩..한칸씩..
}Ptime[9] = frametime;; //최근에 구한값을 맨 뒤에 넣는다.
unsigned framerate = 1000 * 10 / frametime; // * 10은 10프레임의 값이니까..해주는것..
여기서도 매번 프레임마다 찍히니까..
if ( gCounter % 60 == 0 )
{
cout << "frameInterval:" << frameInterval10/10;
cout << " FrameRate:" << 10*1000/frameInterval10 << endl;
}
+gCounter이렇게 60프레임이 한번씩 출력하는것도 나쁘진 않다.
어쩄든 여기까진 프레임확인이고..달라지는 프레임을 어떻게 다뤄야할까..??
1. 프레임을 고정한다 ( 고정프레임) : 콘솔게임에서 주로사용
느린것을 빠르게 할 순없지만, 빠른것을 느리게 한다.
너무 빠르다면(위의 코드를 기준으로..)
unsigned int endTime = PTime + 16; //예정시간
while(true)
if(time() >= endTime)
break;
이런식으로..이전 프레임 시각에서 16밀리초 이상 경과하지 않으면 다음으로 넘어가지 않게 한다..
근데 권장 방법은 아니다.
이처럼 빙글빙글 돌면서 시간때우는것을 비지루프라고 한다.
확실하게 시간을 지연시키지만, 할일도 없으면서 cpu를 가동상태로 만든다는 단점이 있다.
차라리 할일이 없다면 재우는게..여기서 sleep() 함수는 밀리초 (1/1000) 단위 시간으로 예를 들어 1을 넘기면 1밀리초 동안 잔다.
while(time() < endTime)
sleep(1);
이런식..인가?
어쩃든 고정프레임의경우 2가지 방법. 비지루프 또는 재우기로 하면된다.
마지막 한가지!시간을 저장하는 unsigned int가 무한수는 아니다..그래서 언젠가는 값이 꽉 찰텐데..예를들어 PTime 0xffffffff 근처라면.. 위에서 16을 예정시간으로 더하는데..더해버리면되감아져 0 부근이 된다...이때 time()함수가 반환하는것도 0xffffffff근처라면..endTime보다 크므로 전혀대기하지 않고 루프를 빠져나가게 된다.이해하기 쉽게..unsigned 최대치가 100이라고 하면위에 PTime이 98이면 16을 더했을때 endTime은 14가 된다.(endTime은 PTime + 16이다)그리고 time()이 반환하는 값이 99라면 이는 당연히 14보다 크니까 루프를 빠져나가게 된다.해결책은 time() 값과 PTime값을 빼서 음수라면 time이 숫자가 넘어가서 0이나 3되면 음수나오니까그 값에 unsigned의 최대치를 더하면 된다는데..위의 문제는 알아서 해결되더라..뭐지..
그러나 고정 프레임의 문제점이 있다!
느린것을 빠르게 할 수 없다는것 이게 가장 큰 결점이고..결국 가장 느릴때에 맞춰야한다는것.
제일 느릴때가 50밀리 초라고 하면 99% 장면에서 16밀리초로 돌아간다 해도 50밀리초에 맞게 만들어야한다.
2. 가변 프레임 레이트(프레임 레이트에 맞게 게임 속도를 바꾸는 방법) : pc게임 대부분
가변 프레임 레이트에서 이동 속도 등은 프레임에 의존하지 않는 초를 단위로 설정하면 된다.
예를들어 이동속도가 매 초당 60픽셀이라면 이전 프레임에서 현재 프레임까지 걸린시간을 t초라하고
60 * t 픽셀만큼 이동하면 된다.
t가 0.0166(1/60초)이라면 1픽셀 이동, t가 0.0333(1/30초)라면 2픽셀 이동하는 식이다.
시속 100km 2시간을 달리면 200k를 이동하게 된다는 계산을 매 프레임마다 하는것..
이방식의 이점은 크게 두가지로 볼 수 있다.
한가지는 컴퓨터 성능을 극한까지 끌어낼 수 있고 다른 한가지는 처리지연이 발생하지 않는다.
처리지연이란 잠자는 시간이 없다는것..어쨌든 1초에 1프레임밖에 갱신할 수 없는 상황이 되었다고 해도 1초만큼의 거리는 확실하게 이동한다..
결점은
프레임 간격이 계속 변한다는 것을 전제로 게임처리를 작성해야만 하므로 코드가 복잡..
시간에 따라서 계산 내용도 달라지고..시간이 같다고 같아지는건 아니다
2씩 5번과 5번씩 2번 움직인건 동일하다는 보장이 없으니까....
1. update() 인수로 시간 간격을 밀리초 단위로 건넨다 // 루프로 최신화 해야하는것들..
2. MoveCount를 밀리초 단위로 가진다.
3. 이동 중 카운트 최대값은 밀리 초로 지정한다.
지금까지 고정
'@ 16. 1 ~ 17. 1 > 게임프로그래밍의 정석' 카테고리의 다른 글
보간법 (0) 2015.01.06 혼합공식 (0) 2014.07.23 (챕터 06) 문자를 출력하는 방법 (0) 2014.03.02 (챕터 03) 이미지 파일을 사용하자(2) (0) 2014.03.01 싱글톤 클래스 (0) 2013.05.06