게임 엔진의 기초틀을 만들 것이다.

Game Class를 만들 것인데, 추후 다른 게임을 만들 때 이 Class를 상속받아서 만들 것이다.
이제 만들 Game Class는 그래픽을 담당하는 Graphics Class와 입력을 담당하는 Input Class를 포함하고 있다. Graphics Class는 이전 포스팅에서 만들었던 DirectX와 비슷하므로 다른 점을 설명하고 넘어갈 것이다.

Game Class를 만드는 법을 살펴보자!


(Game Class의 include)


Game Class는 graphics 클래스와 input 클래스를 포함한다.

Game Class를 만든다.



(Game Class의 protected)


Game 클래스의 변수들을 담아두었다. private 대신 protected를 사용한 이유는 Game 클래스로부터 상속받은 클래스에게 변수의 직접적인 접근을 허용하게 하기 위해서이다.


public의 메서드들을 만든다.


(생성자와 소멸자)


생성자와 소멸자이다.
생성자는 input 클래스의 객체를 만든다. 다음 포스팅글에서 얘기하겠지만 Input 클래스의 생성자에서 기초적인 입력에 관한 버퍼들을 초기화 한다.
생성자에서 기초적인 초기화를 하고 추가적인 초기화가 없으므로 initialized 라는 변수에 false를 담아둔다. 후에 이 변수를 보고 game 클래스의 초기화를 시켜줄 것이다.

소멸자는 Game 클래스가 삭제될 때 예약된 메모리를 해제하는 순서로 구성되어있다.



(윈도우 메시지 핸들러)


위에는 윈도우 메시지를 처리하는 핸들러 함수를 Game 클래스 안에서 만들었다.
각 이벤트에 대한 처리를 담당한다.

기존에 있던 winmain.cpp 에 있던 WinProc 의 메시지 핸들하는 부분이 Game 클래스로 옮겨 졌으므로 winmain.cpp의 WinProc 도 수정해주어야한다.



(WinProc 수정)


game클래스 안의 messageHandler를 호출해준다.

Game 클래스의 초기화를 담당하는 메서드이다.



(Game클래스의 초기화)



인자로는 윈도우 핸들러를 받는다.
여기서 Graphics 객체를 생성한다. 그 후 그래픽 객체를 초기화 하고, input 객체를 초기화한다.
마지막의 타이머 사용은 frametime을 계산하기 위한 것이다.

  화면에서 움직이는 그래픽 아이템들이 정지된 이미지를 적절한 시간에 연속적으로 화면에 보여주므로써 움직이는 것처럼 보이는 것이다. 이 프레임시간이 너무 빠르면 그래픽 아이템이 너무 빠르게 움직일 것이고, 너무 느리면 그래픽 아이템이 화면에서 너무 느리게 움직일 것이다. 그러므로 우리는 적절한 시간을 간격으로 화면에 출력해주어야한다. (우리가 원하는 속도로)

  그 계산을 위해 고성능의 타이머를 사용한다. 요즘 PC에는 기본적으로 고성능의 타이머를 갖추고 있으므로 이것을 사용할 것이다. QueryPerformanceFrequency 함수를 이용해 timerFreq에 고성능 타이머의 발생 빈도를 저장한다. 그리고 QueryPerformanceCounter함수를 이용해 시간을 가져오는데 이 때 timeStart 변수에 시작 시간을 가져온다. 
  그 후 초기화를 진행 하였으므로 initialized 변수에 true라고 저장한다.

  그 다음 처리해야할 문제는 디바이스 로스트 상태이다.
디바이스 로스트 상태란 Direct3D 그래픽 디바이스를 놓친 상태이다. 예를 들어 우리가 게임을 하다가 Alt-Tab을 눌러서 다른 화면을 보다가 다시 게임을 틀면 게임이 나와야한다. 이 때 게임이 화면에 그려지지 않는 상태가 그래픽 디바이스 로스트 상태이다.

  그렇기에 이러한 상황을 처리해주어야한다. 디바이스 로스트 상태가 되면 리셋하고 리소스를 다시 생성해야한다.



(디바이스 로스트 처리)


  처리 알고리즘은 위와 같다.
이 때 주의할 점은 리셋 함수를 호출하기 전 기존에 할당되었던 모든 리소스를 해제하지 않으면 reset 함수가 실패한다. 

  위에서 사용한 getDeviceState와 reset 함수는 아래와 같다.


(getDeviceState, reset)


getDeviceState는 로스트 상태를 확인하기 위한 함수이다. 함수 내부를 보면 디바이스객체의 TestCooperativeLevel 함수를 사용하는데 이 함수는 매개변수 없이 실행이 되고 성공하면 D3D_OK를 반환하고 로스트 상태를 3가지로 반환한다.
D3DERR_DEVICELOST : 디바이스가 로스트 상태가 되서 현재 복구할 수 없다.
D3DERR_DEVICEENOTRESET : 디바이스를 다시 작동할 수 있다.
D3DERR_DRIVERINTERNALERROR : 디바이스에 내부 에러가 있다. 사용자에게 보고하지만, 할 수 있는 방법이 없다.

또 releaseAll 함수와 resetAll 함수이다.



(releaseAll, resetAll)


비워둔채 메서드만 생성해둔다.

  그 다음 구현해야할 것이 게임 그래픽 렌더링이다. 그래픽을 그리는 과정을 렌더링이라고 부른다.
렌더링은 DirectX의 BeginScene 함수를 호출해 신을 시작하고, 모든 렌더링이 끝난 후 DirectX의 EndScene 함수를 호출해 신을 끝낸다. 이 Scene 밖에서 렌더링을 하면 실패한다.

그러므로 graphics 클래스 안에 beginScene함수와 endScene을 만들어준다.



(beginScene, endScene)


beginScene에서 백버퍼를 지워준다. 그리고 그래픽 디바이스 객체의 BeginScene() 함수를 호출하고 그 결과를 반환한다.

endScene에서는 디바이스 객체의 EndScene() 함수를 호출한다.

  이 두 함수 사이에서 그래픽을 렌더링해야한다. 실제 게임 아이템을 렌더링하는 renderGame() 함수는 아래와 같이 생겼다.



(renderGame 함수)


beginScene과 endScene 사이에서 렌더링을한다.
백버퍼를 지워 준 후 그 백버퍼에 그래픽을 그린다. 그리고 endScene으로 그리는 것을 마무리한다. 그 후 디바이스 로스트를 체크하고 (로스트상태면 처리해주고)
그래픽 객체의 showBackbuffer 함수를 이용해 화면에 그렸던 백버퍼를 출력해준다.

  이제 메인 메시지 루프에서 계속 호출될 게임클래스의 run 함수를 만들 것이다.



(메인 메시지 루프)


지금 만들 Game의 run 함수는 winmain.cpp의 메인 메시지 루프에서 계속 호출 될 것이다.


(Game 클래스의 run 함수)


  run 함수는 위와 같다. 위에서 아래로 코드를 설명하겠다.

처음 부분으로는 그래픽 객체 확인이다.



(그래픽 객체 확인)


  그래픽 객체를 확인하고 없다면 굳이 run 함수를 실행시킬 필요 없으므로 바로 함수를 종료한다.

  그 후 frametime을 계산하기 위한 부분이 나온다.


(frametime 계산)


  루프 시작전 끝 시간을 가져와서 시작시간의 차이를 구해 frametime을 구한다. 그렇다면 루프 시작할 때 시작시간을 설정하고 끝날 때 끝시간을 설정하여 frametime을 구하면 안될까?
-> 그런 방법으로 frametime을 구한다면 게임 루프 밖의 소요되는 시간을 고려하지 못하게 되므로 실제 렌더링 되는 시간이 정확하지 않게 된다.

  위 코드를 보면 frametime의 최소시간과 최대 시간을 정해둔다. 최소시간을 정해두고 그거보다 빠르다면 게임을 잠깐 재워둔다. 왜(?) -> 굳이 게임을 재워야할까 싶긴 하지만 이건 절전을 위한 것이다. 빠르게 처리 되었을 때는 잠깐 쉬게한다면 CPU 부하를 줄여주고 환경 친화적인 프로그램으로 만들어준다. 

이건 앞으로 어떤 프로그램을 만들든지 중요한 부분이다. 우리의 배터리는 소중하므로!

  그 후 frametime을 이용해 평균 fps를 계산해준다. 만약 frametime이 너무 느리다면, 최대 frame 시간으로 frametime을 설정해준다. 보통 이정도 까지 도달할 경우는 거의 없다.

  그 다음부분은 게임동작 관련부분이다.


(게임 함수)


  일시정지 상태가 아니라면 게임이 진행되어야한다.

Game 클래스의 순수 가상함수 update(), ai(), collision() 함수를 실행해준다. 앞으로 만들 게임의 클래스에서 Game 클래스를 상속받을 것인데 그 때 이 함수들을 우리가 채워주는 것이다. 지금은 Game 클래스를 만드는 것으로 게임엔진의 틀을 만드는 과정이다.


(마지막 과정)


다 이루어진 후 renderGame 함수를 이용하여 게임 아이템을 그리고, 컨트롤러의 상태를 읽고 지금까지 받은 키 눌림 입력을 초기화해주면서 끝난다.

(* 참조 - 2D 게임 프로그래밍, 찰스 켈리)

+ Recent posts