(CGP) 3장. 게임의 기본 구조

 

이 장은 게임 제작을 위한 기본 구조와 게임에 적용되는 시간 개념에 설명하고 있으며 최종 프레임워크를 이해하고 그 안에서 프로그래밍하기 위한 내용 입니다.

이 부분은 오늘날 2D, 3D 게임 엔진 기본 구조가 됩니다.

 

슈퍼맨 유튜브 동영상 강의 주소

 

(1) http://youtu.be/9fh0vMNs3kA
(2) http://youtu.be/lcacGPogmtA
(3) http://youtu.be/-yVEUGCpDdI

 

 


 

01 기본 구조

 

게임의 기본 구조는 크게 초기화, 데이터 갱신, 화면 출력, 해제로 나눌 수 있다.

물론 게임의 기본 구조가 반드시 이 요소만으로 실행되는 것은 아니지만 대부분의 게임 구조가 이 구조로 되어 있다.

그러면 이러한 게임의 기본 구조를 세부적으로 살펴보도록 하자.

 

■ 구성 요소

 

게임의 기본 구조는 초기화, 데이터 갱신, 출력, 해제 나누어지며 그 구성 요소는 [그림 3-1]과 같은 순서로 실행된다.

특히 초기화와 해제는 게임이 화면에 출력되기 이전 단계와 게임이 종료된 단계에서 실행되며, 데이터 갱신과 화면 출력은 무한 반복을 통해 실행된다.

이와 같은 흐름을 블록으로 나타내면 [그림 3-1]과 같다.

 

 

[그림 3-1] 게임 기본 구조

 

- 초기화

 

이 부분은 주로 데이터를 초기화한다. 초기화는 실제 게임이 실행되기 이전에 게임에 필요한 기본 데이터를 읽고 각종 변수를 초기화한다.

게임 초기화에 대한 부분은 게임 실행 초기에서 찾아 볼 수 있다.

게임을 처음 실행할 때 보면  'Loading...' 또는 ‘로딩중...‘ 이란 문구가 출력되는 것을 보게 되는데 이 문구가 출력되고 있는 동안에 게임 초기화가 실행된다.

 

초기화의 내용을 살펴보면 캐릭터 초기화, 사운드 초기화, 패턴 또는 AI 초기화, 메모리 할당 등이 있다.

초기화도 크게 게임 전체에 관한 초기화와 각 스테이지별 초기화로 나눌 수 있다.

 

- 데이터 갱신

 

이 단계는 출력에 앞서서 모든 데이터를 갱신하는 단계이며 이 데이터는 최종적으로 출력에 반영이 된다. 데이터 갱신은 크게 입력장치에 의한 데이터 갱신과 스스로 데이터를 갱신하는 경우로 나눌 수 있다. 입력장치에 의한 데이터 갱신은 주로 키보드와 마우스에 의해 데이터가 갱신되는 것을 말하며 주로 주인공의 데이터 갱신이 이에 해당된다.

스스로 데이터를 갱신하는 경우는 인공지능, 충돌, 패턴, 물리 등에 의해 데이터가 갱신되는 경우를 말한다.

 

- 화면 출력

 

게임에서 화면 출력은 상당히 중요하다. 왜냐하면 게임은 화면에 출력된 캐릭터를 이용하여 진행하기 때문이다. 또한 화면에 출력된 내용은 대부분 데이터 갱신을 통해 확정된 데이터를 문자 또는 이미지로 출력한 것이다.

 

- 해제

 

해제는 동적으로 할당된 메모리를 해제하는 경우가 대부분이지만 초기화에서 생성한 객체를 해제하는 역할도 한다. 여기서 객체는 사운드 엔진, 물리 엔진, 그래픽 엔진(DirectX, OpenGL)등을 말한다.

 

■ 함수

 

프로그래밍에서 [그림 3-1]과 같이 나눠진 구조를 쉽게 구분하여 실행하는 방법으로 함수가 있다. 함수는 역할도 중요하지만 함수명 또한 중요하다.

함수명만으로 함수의 역할을 파악할 수 있게 하는 것은 오래된 프로그래밍의 관례이기도 하다. 그러면 [그림 3-1]의 게임 기본 구조를 함수로 제작한다면 어떻게 함수명을 정할 수 있을까? 아래 [표 3-1]은 게임 기본 구조에 사용할 함수들이며 주로 게임 엔진에 사용되는 함수명으로 되어 있다.

 

 

함수명

기본 구조

Init()

초기화 함수

Update()

데이터 갱신 함수

Render()

화면 출력 함수

Release()

해제 함수

 

[표 3-1] 함수명

 

■ C언어를 이용한 게임 기본 구조의 제작

 

이제 [표 3-1]에 정의한 함수들로 [그림 3-1]과 같은 프로그래밍 구조를 만들어 보자.

게임 프로그래밍을 하기 전에 이와 같은 구조를 만드는 것은 다음과 같은 이유 때문이다. 요즘 게임을 제작할 때 혼자서 제작하는 경우는 드물다. 그만큼 게임 제작에 들어갈 코드 규모가 상당히 커졌고 분업화되었다.

이러한 환경에서 하나의 게임을 여러 사람이 나누어 작업하는 경우를 생각해 보자.

 

코드를 통합하여 하나의 게임으로 만들기가 쉬울까? 만약 서로 다른 환경에서 작성한 코드라면 환경 설정 문제로 그래픽 엔진(DirectX, OpenGL)이 다운되는 경우가 많을 것이다.

이러한 이유 때문에 게임의 기본 구조를 설명하고 그 구조를 만들어 모든 게임에 적용하는 것이다.

이 개념은 이 장의 마지막에 제작하는 ‘06 프레임워크’ 에서 자세히 살펴볼 것이다.

 

 

구분

역할

엔진 프로그래머

DirectX와 OpenGL 그래픽 엔진을 이용하여 게임 전체에 적용될 라이브러리를 제작하거나 게임의 기본 환경을 만드는 역할을 한다.

툴 프로그래머

Win32 API, MFC, C#등을 이용하여 게임 저작 툴을 만들며 엔진 프로그래머들이 만든 라이브러리를 이용하여 게임 환경을 툴에서 테스트할 수 있도록 한다.

UI 프로그래머

게임에 사용되는 버튼, 마우스, 인벤토리창등과 같은 인터페이스를 만드는 역할을 한다. 기본 환경은 엔진 프로그래머들이 제공하는 것을 사용한다.

이펙트 프로그래머

게임에 사용될 각종 효과를 제작하며 기본 환경은 엔진 프로그래머들이 제공하는 것을 사용한다.

네트워크 프로그래머

온라인 서버에 대한 모든 프로그래밍을 담당한다.

 

[표 3-2] 게임 프로그래머의 역할

 

C언어로 구성한 게임 기본 구조

 

[그림 3-1]을 구성하기 위해 필요한 문법은 함수와 반복문 외에는 다른 것이 없음을 알 수 있다. 먼저 이 개념부터 확인하자.

프로그램은 순차적인 실행을 한다. 그 실행은 main() 함수 안에서 이루어진다.

이 점을 이해하는가? 그렇다면 [그림 3-1]에 대응되는 함수 4개는 다음 [소스 3-1]과 같이 나열할 수 있다.

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

#include <stdio.h>

 

void Init()

{

}

void Update()

{

}

void Render()

{

}

void Release()

{

}

 

int main(void)

{

     Init();        // 초기화

     Update();    // 데이터 갱신

     Render();    // 화면 출력

     Release();   // 해제

     return 0;

}

 

[소스 3-1] 기본 구조를 제작하기 위한 함수 선언

 

[소스 3-1]을 실행하게 되면 프로그램 시작과 동시에 종료하게 된다. 이것은 18행부터 21행까지를 순차적으로 실행한 후에 22행의 return 0;을 만나기 때문이다.

기본적으로 위의 4개의 함수는 정확히 한 번씩 실행되고 종료하게 된다.

이 구조를 보면 Init()와 Release() 함수는 제대로 실행한 것이지만 Update()와 Render()는 그 역할을 수행하지 못한 것임을 알 수 있다.

 

[그림 3-1]에서와 같이 Update()와 Render()가 무한 반복될 수 있도록 반복문을 적용해 보자. 반복문에 사용되는 구문으로는 while문과 for문, do while문이 있다.

그 중 while문 또는 for문을 무한 반복문에 사용하도록 한다.

반복문을 무한 반복하게 만드는 방법은 반복의 조건에 해당되는 부분을 항상 1로 만들거나 조건 자체를 두지 않는 방법이 있다.

아래의 [표 3-3]의 예는 모두 무한 반복을 하는 형식이다.

 

 

while( 1 )

{

 

}

for( ;  ;  )

{

 

}

 

[표 3-3] 무한 반복문

 

이 무한 반복문을 [소스 3-1]에 적용해 보면 아래 [소스 3-2]와 같다.

 

 

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

#include <stdio.h>

 

void Init()

{

}

void Update()

{

}

void Render()

{

}

void Release()

{

}

 

int main(void)

{

     Init();        // 초기화

         

     while( 1 )

     {

         Update();    // 데이터 갱신

         Render();    // 화면 출력

     }

     

     Release();   // 해제

     return 0;

}

 

[소스 3-2] 완성된 기본 구조

 

[그림 3-1]을 소스로 옮긴 [소스 3-2]를 비교해 보면 그 구조가 코드로 잘 옮겨진 것을 알 수 있다.

여기까지가 개념을 코드로 옮긴 것이며 이것이 바로 프로그래밍이다.

즉 개념을 설계하고 코드를 적용할 수 있는 능력이 바로 프로그래밍 능력이다.

 

 


02 대기

 

[소스 3-2]에 게임 기본 구조가 잘 적용되어 있지만 콘솔창에서 이 구조를 사용하기에는 적잖은 문제가 있다. 기본적으로 게임은 화면에 이미지를 그리고 지우는 과정을 반복한다.

이때 DirectX와 OpenGL과 같은 그래픽 엔진은 이 과정을 빠르게 할 수 있도록 해주지만  우리가 제작하려는 콘솔창에서 이 과정을 실행하면 화면의 깜빡임이 발생하게 된다.

그 이유는 콘솔창에서는 고속으로 화면을 지우고 그리게 하는 부분이 없기 때문이다.

 

이 과정은 Render() 함수에서 일어나게 되는데 while문의 반복 속도는 CPU성능에 비례하므로 CPU 성능이 좋으면 좋을수록 깜빡임은 더욱 많아지게 된다.

이때 비디오 카드의 속도와 모니터와의 속도차이도 이와 같은 현상을 가속화시킨다.

 

이런 문제를 해결하기 위해 한 장면을 출력한 후에 약간의 대기 시간을 두어 실행속도를 조금 늦추는 방법을 사용하면 CPU성능이 좋아도 일정한 횟수이상을 반복하지 않게 된다.

 

일반적으로 인간은 1초에 30번 이상의 화면 전환이 발생하면 착시현상에 의해 부드럽게 연결된 동작으로 인식하게 된다. 이 원리를 이용하여 기본 구조에서 발생하는 깜빡임을 최소화하고 게임 진행 속도를 제어하면 다음과 같다.

 

1초에 약 30번의 화면 전환이 일어나도록 하기 위해서는 while문의 시작 부분부터 Render()가 끝나는 곳까지 걸린 시간을 이용한다.

컴퓨터에 사용하는 시간은 밀리세컨드(millisecond) 이므로 1초는 1000 밀리세컨드가 된다.

이 과정에서 1초에 30번 실행되기 위해서는 한번 화면이 출력되는데 약 33 밀리세컨드가 걸리면 된다. 즉 1초인 1000 밀리세컨드를 30으로 나누면 한 장면을 출력하는데 걸리는 시간이 아래 [표 3-4]와 같이 나오게 된다.

 

 

 

[표 3-4] 한 장면을 출력하는데 걸리는 시간

 

[그림 3-2] 한 장면을 출력하는데 걸린 시간의 영역

 

이를 구현하기 위해서 한 장면 출력하는데 걸리는 시간이 약 33 밀리세컨드 미만이면 대기 상태에 있게 하고, 33 밀리세컨드 이상이면 while문을 다시 반복하게 하면 1초에 약 33번을 반복하게 된다. 여기까지의 사항을 코드로 나타내면 아래 [소스 3-3]과 같다.

 

 

1

2

3

4

5

6

7

8

9

while( 1 )

{

   CurTime = clock();       // 현재 시각

   if( CurTime - OldTime > 33 )

   {

      OldTime = CurTime; 

      break;

   }

}

 

[소스 3-3] 대기 코드

 

4행을 보면 현재 시각과 이전 시각의 차이를 계산하여 33 밀리세컨드이면 6행과 같이 현재 시각을 이전 시각을 저장하는 OldTime에 대입하여 이때의 시각에서부터 다음 현재 시각까지의 차이를 계산할 수 있게 한다.

그리고 7행의 break를 만나면 1행의 무한 반복을 종료한다.

이와 같은 전체 과정을 코드로 나타내면 아래 [소스 3-4]와 같다.

 

 

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

#include <stdio.h>

#include <time.h>

 

void Init()

{

}

void Update()

{

}

void Render()

{

}

void Release()

{

}

 

int main(void)

{

     clock_t CurTime, OldTime;

     Init();        // 초기화

     

     OldTime = clock();   

     while( 1 )

     {

         Update();    // 데이터 갱신

         Render();    // 화면 출력

 

         while( 1 )   // 대기 상태 진입

         {

              CurTime = clock();

              if( CurTime - OldTime > 33 )

              {

                  OldTme = CurTime;

                  break;

              }

          }

     }

     

     Release();   // 해제

     return 0;

}

 

[소스 3-4] 대기 상태가 적용된 게임 기본 구조의 전체 소스

 

위의 23행부터 37행까지의 내용은 다음 코드로 바꾸어도 된다.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

     while( 1 )

     {

         OldTime = clock();

 

         Update();    // 데이터 갱신

         Render();    // 화면 출력

 

         while( 1 )   // 대기 상태 진입

         {

              CurTime = clock();

              if( CurTime - OldTime > 33 )

                  break;

        }

     }

 

[소스 3-5] 대기 코드(2)

 

 


03 게임 요소 동기화

 

게임은 캐릭터, 패턴, 충돌, 인공지능과 같은 많은 요소로 이루어져 있다. 그 중에서 주인공은 플레이어에 의해 움직이며, 그 외의 요소들은 자체적으로 설정된 시간 또는 값에 의해 스스로 움직인다. 만약 요소 간에 움직임의 기준이 다르다면 엉뚱한 시간에 개별적인 동작을 하게 될 것이다. 그래서 동기화는 게임 프로그래밍에선 필수이다.

동기화하는 방법으로 여러 가지가 있지만 대표적인 두 가지 방법만 살펴보자.

 

■ 프레임값을 기준으로 하는 방법

 

이 방법은 화면 전환이 일어날 때마다 값을 1씩 증가하는 프레임 변수를 두고 현재 프레임과 이전 프레임의 차이를 계산하여 이동 또는 데이터를 갱신하는 방법을 말한다.

예를 들어 1초에 대략 30번 화면 전환이 발생하고 프레임 변수값이 60이 되었다면 경과된 시간은 2초가 된다. 이와 같은 계산으로 프레임에 따른 적 캐릭터의 출현을 [그림 3-3]과 같이 설정할 수 있으며 각 게임 요소를 프레임으로 동기화시킨다.

[그림 3-3] 프레임에 따른 캐릭터 출력

 

이 과정을 코드로 옮기면 [소스 3-5]와 같다.

 

 

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

#include <stdio.h>

#include <time.h>

 

int g_nFrameCount;   // 프레임 변수

 

void Init()

{

}

void Update()

{

}

void Render()

{

}

void Release()

{

}

 

int main(void)

{

     clock_t CurTime, OldTime;

     Init();        // 초기화

     

    OldTime = clock();   

     while( 1 )

     {

         Update();    // 데이터 갱신

         Render();    // 화면 출력

 

         while( 1 )   // 대기 상태 진입

         {

              CurTime = clock();

              if( CurTime - OldTime > 33 )

              {

                  OldTme = CurTime;

                  break;

              }

          }

          g_nFrameCount++;

     }

     

     Release();   // 해제

     return 0;

}

 

[소스 3-5] 프레임을 기준으로 동기화하기 위한 기본 구조

 

위의 소스 4행을 보면 g_nFrameCount 변수가 전역 변수로 선언된 것을 볼 수 있다.

이처럼 전역 변수로 선언한 이유는 여러 개체에서 이 변수값을 참조하여 동기화하기 때문이다. 39행을 보면 한 장면의 출력이 끝나면 g_nFrameCount값은 1씩 증가한다.

 

■ 시간 간격을 이용하는 방법

 

이 방법은 오늘날의 대부분의 게임에서 사용하는 방법이며 앞으로 제작하는 게임에 주로 사용하는 방법이다.

 

시간 간격을 이용한 방법으로 적 캐릭터의 이동에 대해 살펴보자.

적 캐릭터마다 이동하는 시각이 다르다면 적 캐릭터는 이전에 이동한 시각과 현재 시각 차이를 계산하여 이동을 결정하게 된다. 이때 이동이 결정되면 현재 시각은 다음 이동을 위해 이전 이동 시각의 변수에 저장하여 다음번 시간 차이를 계산할 때 사용하게 된다.

이를 간단히 코드로 나타내면 아래 [소스 3-6]과 같다.

 

 

1

2

3

4

5

6

7

8

9

CurTime = clock(); // 현재 시각

 

if( CurTime - OldMoveTime > 200 )

{

   OldMoveTime = CurTime;

   // 캐릭터 이동

   nX++; 

   nY++;

 

[소스 3-6] 시간 간격에 의한 적 캐릭터의 이동

 

 

 

아래 [그림 3-4]는 게임이 시작된 시각과 현재 시각의 차이를 계산하여 적 캐릭터가 출현하는 예이다.

 

[그림 3-4] 시간 차이에 따른 캐릭터 출현

 

[그림 3-4]에서 맨 처음 두 개의 적 캐릭터 출현 시각이 2000이라는 것은 게임이 시작된 후 2초일 때 출현하는 것을 의미한다. 만약 경과된 시간이 2100이라면 맨 앞의 두 마리 적 캐릭터는 출현하게 되며 나머지 적 캐릭터는 대기 상태에 놓이게 된다.

 

 


04 키보드 처리

 

화면으로부터 입력 받은 키 값을 처리하려면 먼저 키 입력을 감지해야 한다. 이를 위해 C 표준 함수인 _kbhit()와 _getch()를 활용하여 보자.

이 함수의 사용 방법은 2장에서 이미 다루었던 내용이므로 생략하도록 하고 프로그래밍적인 구조에 대해 집중해 보자.

 

키 입력은 게임의 흐름을 바꿀 수 있는 유일한 수단이다.

즉 게임을 종료하거나 주인공을 이동시키는 역할을 하며 이와 같은 동작은 데이터를 변경하게 한다. 그러므로 키 처리는 데이터 갱신인 Update()함수에서 처리할 수 있지만 여기서는 키 처리 부분과 Update()에서 주요하게 다루는 충돌, 이동, 인공지능, 네트워크, 물리 등을 구분하여 프로그래밍한다.

 

_kbhit()와 _getch()는 모두 입력 스트림을 사용하므로 키보드 버퍼의 내용에 예민하게 반응한다. 기본적인 흐름은 무한 반복문을 실행하면서 키 입력이 있으면 키보드 버퍼로부터 입력된 키 값을 가져와 처리하는 것이다.

이와 같은 기본적인 내용을 코드로 옮기면 아래 [소스 3-7]과 같다.

 

 

01

02

03

04

05

06

07

08

09

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

경축! 아무것도 안하여 에스천사게임즈가 새로운 모습으로 재오픈 하였습니다.
어린이용이며, 설치가 필요없는 브라우저 게임입니다.
https://s1004games.com

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

#include <stdio.h>

#include <conio.h>

#include <time.h>

 

void Init()

{

}

void Update()

{

}

void Render()

{

}

void Release()

{

}

 

int main(void)

{

     int nKey;

     clock_t CurTime, OldTime;

     Init();        // 초기화

     

     OldTime = clock();   

     while( 1 )

     {

        if( _kbhit() )

        {

            nKey = _getch();

            if( nKey == 'q' )

               break;

            switch( nKey )

            {

            case 'j' :

                     break;

            case 'l' :

                     break;

            }

        }

 

         Update();    // 데이터 갱신

         Render();    // 화면 출력

 

         while( 1 )   // 대기 상태 진입

         {

              CurTime = clock();

              if( CurTime - OldTime > 33 )

              {

                  OldTme = CurTime;

                  break;

              }

          }         

     }

     

     Release();   // 해제

     return 0;

}

 

[소스 3-7] 키 입력 처리

 

위의 소스에서 키 입력 처리는 27행부터 39행까지이다.

그 중에서 30행과 31행의 내용이 32행부터 38행의 switch()문 안으로 들어가야 할 것으로 생각할 수 있다. 이와 같이 switch()문에서 제외시킨 이유는 다른 키 입력에 비해 가장 우선순위가 높기 때문이다.

 

'q'키는 게임을 종료하는 키로 많이 사용되며 이 키가 눌려지면 게임 종료와 함께 main()을  종료하게 된다. 이처럼 게임 종료 키는 다른 키에 비해 가장 우선순위가 높으므로 가장 먼저 처리하기 위해 switch()문에서 제외시킨 것이다.

 

31행의 break는 25행의 while문을 종료하게 만드는 가장 간단한 방법이다.

31행의 break;가 실행되고 나면 제어는 55행으로 가서 Release()와 return 0;을 실행하고 프로그램은 종료된다.

 

 


05 게임 프로그래밍 용어

 

여기서 언급하는 게임 프로그래밍 용어는 앞으로 제작할 게임과 DirectX와 OpenGL, 스마트폰 게임을 제작할 때 공용으로 사용되는 용어이다.

이런 포괄적인 내용을 설명하는 이유는 다음 절에서 구현하게 될 프레임워크에 개념적으로 사용되기 때문이다.

 

여기서 언급하는 용어는 전부 번역된 용어로 소개하고 있지만 대부분 번역된 용어보다는 영문 용어 자체를 많이 사용하고 있으므로 양쪽 용어를 모두 익혀두도록 하자.

 

■ 전위 버퍼(primary buffer)

 

전위 버퍼를 전위면이라고도 한다.

이 전위 버퍼는 화면과 일대일 대응되는 메모리를 말하며 그래픽 카드의 메인 메모리의 일부분이다. 현재 모니터의 해상도를 1024*768에서 1920*1080 으로 변경한다면 이 전위 버퍼에 해당되는 메모리 또한 늘어나게 된다.

또한 한 점의 색상을 출력하기 위한 비트수가 16 비트 또는 32 비트인가에 따라서도 크기는 달라진다.

 

전위 버퍼에 저장되는 모든 데이터는 색상 정보이며 모니터는 이 전위 버퍼의 내용을 그대로 색상으로 출력한다.

 

[그림 3-5] 전위 버퍼

 

■ 후위 버퍼(back buffer)

 

후위 버퍼는 전위 버퍼와 동일한 특성을 가진 메모리를 말한다.

전위 버퍼는 모니터와 일대일 대응되므로 컴퓨터를 동작함과 동시에 그래픽 카드에 생성이 되지만 후위 버퍼는 따로 생성이 된다.

후위 버퍼는 그래픽 카드 메모리에 생성될 수 있지만 시스템 메모리에도 생성될 수 있다. 후위 버퍼는 전위 버퍼와 특성이 같으므로 전위 버퍼와 연결하여 사용하며 주로 시스템 메모리 보다는 그래픽 카드 메모리에 생성하여 빠른 화면 전환을 하기 위해 사용된다.

후위 버퍼는 그래픽 엔진(DirectX, OpenGL)에서도 많이 언급이 되는데 후위 버퍼라는 용어보다는 백 버퍼라는 용어를 많이 사용한다.

 

[그림 3-6] 후위 버퍼

 

■ 이중 버퍼링(double buffering)

 

이중 버퍼링은 두 개의 버퍼를 이용하여 화면을 전환하는 방법을 말한다.

여기서 사용되는 두 개의 버퍼는 전위 버퍼와 후위 버퍼를 말하며 후위 버퍼의 내용을 전위 버퍼에 복사하여 출력하는 것을 이중 버퍼링이라고 한다.

그러면 왜? 이와 같은 방식이 필요한가를 생각해 보자.

전위 버퍼가 모니터와 일대일 대응되고 있는 순간에 전위 버퍼의 내용을 변경하게 되면 변경된 내용의 일부만 보여 지거나 이로 인해 깜빡임 또는 화면이 찢어지는 테어링(tearing) 현상이 발생된다. 또한 모니터의 화면 속도와 메모리 간의 속도 차이로 이런 현상이 발생하기도 한다.

이 현상을 줄이기 위해서 백 버퍼에 다음에 그려질 모든 내용을 미리 그려놓고 전위 버퍼에 한꺼번에 복사하는 방식을 사용한다. 이 복사 방식은 메모리와 메모리간의 복사이므로 앞에 언급한 티어링 현상과 깜빡임을 상당히 줄여준다.

 

[그림 3-7] 이중 버퍼링

 

■ 페이지 전환(page flipping)

 

이중 버퍼링에 의해 메모리를 복사하는 방식을 사용하였지만 이 방식도 복사하는데 시간이 걸린다. 페이지 전환은 오늘날 모든 게임에서 사용하는 방식으로 실제로 전위 버퍼와 후위 버퍼간의 메모리 복사가 아닌 화면과 일대일 대응하는 메모리의 시작 주소를 바꾸는 방식이다. 예를 들어 현재 비디오 메모리의 0xa000:0000 번지가 전위 버퍼의 시작 메모리 주소라면 대기하고 있던 백 버퍼의 0xb800:0000을 전위 버퍼의 메모리 주소로 설정하여 한번은 백 버퍼의 내용을 출력하게 하고 다음번에는 그와 반대로 설정하여 출력하게 한다.

이와 같은 반복 동작은 전위 버퍼의 메모리와 백 버퍼의 메모리를 번갈아 가면서 출력하게 하는데 이 동작을 페이지 전환이라고 한다.

 

[그림 3-8] 페이지 전환

 

 


06 프레임워크

 

 

프레임워크란? 코드를 작성하기 위해서 제공되는 프로그래밍 틀을 말한다.

즉 프로그래머의 의도에 따라 아무 곳에나 프로그래밍을 하는 것이 아니라 정해진 구조 안에 정해진 내용의 코드를 작성하는 구조를 말한다.

 

잘 만들어진 프레임워크는 팀 전체의 코드 통합과 향상을 가져온다.

특히 게임 제작이 대규모로 이루어지는 오늘날에는 개개인이 작성한 프로그램을 하나로 통합하는 것도 상당히 어려운 일이다. 하지만 통합된 구조 안에서 작성한 코드는 구조의 역할에 따라 코드가 나눠지므로 개개인의 코드를 하나로 통합하기가 쉽다.

이처럼 프레임워크는 팀 단위로 하나의 프로그램을 만들고자 할 때 상당히 유용하게 사용된다.

 

앞서 우리가 살펴본 게임의 기본 구조와 그 구조에 따른 역할을 기억하는가?

이것은 콘솔창에서 게임 프로그래밍을 하기 위한 프레임워크에 해당이 된다.

이와 같은 예는 DirectX SDK의 샘플예인 [그림 3-9]의 EmptyProject 소스를 봐도 알 수 있다.

 

[그림 3-9] DirectX SDK의 Empty Project 소스

 

위의 [그림 3-9]의 소스를 보면 'Set the callback functions'이란 주석을 볼 수 있다.

이것은 어딘가에서 DirectX를 이용하여 렌더링을 할 때 호출되는 함수로 이와 같은 함수를 콜백(call back) 함수라고 한다. 프로그래머가 이 구조 안에서 프로그래밍을 하려면 콜백 함수마다 정해진 역할에 맞는 코드를 작성해야 하며 작성된 코드는 적절한 시점에 호출된다.

 

이와 같은 프레임워크를 사용하면 장점도 있지만 단점도 있다.

그 단점 중의 하나는 이미 정해진 구조 안에서 프로그래밍을 하게 되므로 프로그래밍에 관한 전체적인 설계 능력이 현저히 떨어질 수 있다는 것이다.

하지만 단점에 비해 장점이 많으며 오늘날 프레임워크는 계속 진화하고 있으므로 많은 프로그래머가 사용하고 있다.

 

■ 프레임워크에 사용되는 함수

 

우리가 작성할 프레임워크는 콘솔창에서 게임 프로그래밍을 하기 위한 것이다.

콘솔창에서 게임 프로그래밍을 하려면 많은 제약사항이 있다는 것을 2장에서도 언급하였다.

이런 부분을 극복하기 위해 C 표준 함수가 지원하지 못하는 부분을 윈도우 함수를 사용하여 해결할 수밖에 없음을 언급을 했었다.

여기서도 윈도우 함수를 일부 사용한다.

 

게임 프로그래밍 용어에서 전위 버퍼와 후위 버퍼 그리고 페이지 플리핑(flipping)의 개념을 살펴보았듯이 하나의 콘솔창에 전위 버퍼와 백 버퍼의 역할을 할 수 있는 화면 버퍼를 생성하고 페이지 플리핑을 시켜 콘솔창에서의 한계를 극복하고자 한다.

물론 다른 여러 방법도 있겠지만 이러한 구조는 DirectX와 OpenGL등에서 사용되는 구조이므로 지금부터 C로 이 구조를 설계해 보고 활용한다면 최종적으로 구현하려는 게임 환경에서도 쉽게 적응할 수 있을 것이다. 따라서 앞으로 제작하는 모든 게임에 이 구조를 적용하고자 한다. 기본적인 프레임워크는 [그림 3-1]의 구조와 [표 3-1]에서 소개한 함수를 그대로 사용한다.

 

- 화면 버퍼 초기화

 

화면 버퍼 초기화 함수인 [표 3-5]는 앞서 설명했듯이 전위 버퍼와 백 버퍼로 사용할 화면 버퍼를 두 개 생성한다.

 

 

함수 원형

void ScreenInit()

 

[표 3-5] 프레임워크 초기화 함수

 

- 화면 버퍼 지우기

 

하나의 화면 버퍼가 활성화되어 출력되고 있는 동안에 다음 장면을 위한 화면 버퍼는 지워야 한다. [표 3-6]의 함수는 대기하고 있는 화면 버퍼를 지우는 역할을 한다.

 

 

함수 원형

void ScreenClear()

 

[표 3-6] 대기하는 화면 버퍼를 지우기는 함수

 

- 화면 버퍼 전환

 

화면 버퍼 전환은 활성화된 화면 버퍼와 비활성화된 화면 버퍼의 상태를 바꾸는 것을 말한다. 이것은 마치 게임 프로그래밍 용어에서 살펴본 페이지 플리핑과 같다.

 

 

함수 원형

void ScreenFlipping()

 

[표 3-7] 화면 버퍼 전환 함수

 

- 화면 버퍼 해제

 

화면 버퍼 초기화 함수에서 생성한 두 개의 화면 버퍼를 모두 해제하는 부분이다.

 

 

함수 원형

void ScreenRelease()

 

[표 3-8] 화면 버퍼 해제 함수

 

- 출력 함수

 

대기 화면 버퍼의 x, y 좌표에 문자열을 출력해 준다.

 

 

함수 원형

void ScreenPrint( int x, int y, char* string)

 

[표 3-9] 화면 버퍼 해제 함수

 

- 출력 문자의 색상을 설정하는 함수

 

출력 문자의 색상값은 1에서부터 15까지 설정할 수 있으며 다음 SetColor() 함수가 사용될 때까지 이전에 사용한 SetColor()의 색상이 모든 문자에 적용된다.

이 색상은 대기 화면 버퍼에 적용된다.

 

 

함수 원형

void SetColor( unsigned short color )

 

[표 3-10] 화면 버퍼 해제 함수

 

■ 프레임워크 전체 소스

 

[표 3-5]부터 [표 3-8]까지의 함수를 [소스 3-2]에 적용하면 다음과 같다.

 

 

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

#include <stdio.h>

#include "Screen.h"

 

void Init()

{

}

void Update()

{

}

void Render()

{

     ScreenClear();

     // 출력 코드

 

     ScreenFlipping();

}

void Release()

{

}

 

int main(void)

{

     ScreenInit();

     Init();        // 초기화

         

     while( 1 )

     {

         Update();    // 데이터 갱신

         Render();    // 화면 출력        

     }

     

     Release();   // 해제

     ScreenRelease();

     return 0;

}

 

[소스 3-7] 프레임워크 전체 소스

 

위의 [소스 3-7] 구조는 기존의 DirectX, OpenGL과 유사한 구조로 되어 있다.

특히 Render() 함수의 내용은 아래 OpenGL 및 DirectX의 출력 구조와 동일하다.

 

 

void Render()

{

     glClear( GL_COLOR_BUFFER_BIT );

     glBegin( GL_POLYGON );

     // 출력 코드

 

     glEnd();

     glFlush();

}

 

[소스 3-8] OpenGL 출력 함수

 

 

VOID Render()

{

    // Clear the backbuffer to a blue color

    g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB( 0, 0, 255 ), 1.0f, 0 );

 

    // Begin the scene

    if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )

    {     

        // 출력 코드

 

        // End the scene

        g_pd3dDevice->EndScene();

    }

 

    // Present the backbuffer contents to the display

    g_pd3dDevice->Present( NULL, NULL, NULL, NULL );

}

 

[소스 3-9] DirectX 출력 함수

 

현재 그래픽 엔진도 출력하기 전에 백 버퍼의 내용을 전부 지우고 전위 버퍼와 백 버퍼를 교체하는 방법을 사용하듯이 [소스 3-7]의 12행과 15행에도 이 부분이 적용되어 있다.

[소스 3-7]을 보면 대기 상태를 두어 깜빡임을 줄여준 부분이 없다는 것을 알 수 있다.

이전 [소스 3-4]의 소스는 오직 하나의 화면 버퍼에 그리고 지우기를 반복했으므로 대기 상태를 둘 수밖에 없었지만 윈도우 함수를 활용한 구조에서는 두 개의 화면 버퍼를 사용하므로 이런 대기 상태가 필요 없다.

위와 같이 만들어진 프레임워크는 이제 앞으로 제작하게 되는 모든 게임의 기본 구조로 적용된다.

 

 


07 분할 컴파일

 

[표 3-5]부터 [표 3-10]까지의 함수를 main()이 있는 소스에 넣어 프로그래밍하기 보다는 다른 파일에 저장하고 필요할 때마다 임의로 호출하면 프레임워크 코드와 게임 코드를 구분하여 프로그래밍할 수 있다. 이와 같이 분할 컴파일을 하기 위해서는 현재의 프로젝트에 아래와 같이 소스 파일을 추가하고 main() 함수가 있는 곳에서 Screen.h 파일만 포함하면 된다.

 

프로젝트에 파일 추가하기

 

프로젝트 안에 소스 파일을 추가하기 위해서는 다음과 같이 한다.

먼저 *.c 파일은 솔루션 탐색기에서 프로젝트 안의 소스 파일 폴더를 클릭한 후에 마우스의 오른쪽 버튼을 클릭하여 ‘추가’ 메뉴에서 ‘기존 항목’ 메뉴를 선택한다.

 

[그림 3-10] 기존 항목 메뉴를 선택하기 위한 경로

 

그리고 아래와 같이 해당되는 *.c 파일을 선택하고 하단의 추가 버튼을 클릭한다.

 

[그림 3-11] 추가 파일 선택

 

위의 과정이 끝나면 추가된 *.c 파일을 솔루션 탐색기에서 확인할 수 있다.

헤더 파일의 추가도 이와 같은 방법으로 추가하면 된다.

 

[그림 3-12] 프로젝트 안에 추가된 파일 Screen.c

 

■ 분할된 전체 소스

 

아래의 소스는 Screen.h 파일의 내용이다.

 

 

1

2

3

4

5

6

void ScreenInit();

void ScreenFlipping();

void ScreenScreenClear();

void ScreenRelease();

void ScreenPrint( int x, int y, char* string );

void SetColor( unsigned short color );

 

[소스 3-10] Screen.h

 

아래의 소스는 Screen.c 파일의 내용이다.

소스의 내용은 Win32 API를 공부할 때 살펴보고 여기서는 함수의 사용법만 살펴본다.

 

 

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

#include <windows.h>

 

static int g_nScreenIndex;

static HANDLE g_hScreen[2];

 

void ScreenInit()

{

     CONSOLE_CURSOR_INFO cci;

        

     // 화면 버퍼 2개를 만든다.

     g_hScreen[0] = CreateConsoleScreenBuffer( GENERIC_READ | GENERIC_WRITE, 0,

                                            NULL, CONSOLE_TEXTMODE_BUFFER, NULL );

     g_hScreen[1] = CreateConsoleScreenBuffer( GENERIC_READ | GENERIC_WRITE, 0,

                                            NULL, CONSOLE_TEXTMODE_BUFFER, NULL );

 

     // 커서 숨기기

     cci.dwSize = 1;

     cci.bVisible = FALSE;

     SetConsoleCursorInfo( g_hScreen[0], &cci );

     SetConsoleCursorInfo( g_hScreen[1], &cci );

}

 

void ScreenFlipping()

{               

     SetConsoleActiveScreenBuffer( g_hScreen[g_nScreenIndex] );    

     g_nScreenIndex = !g_nScreenIndex; 

}

 

void ScreenClear()

{               

     COORD Coor = { 0, 0 };

     DWORD dw;

     FillConsoleOutputCharacter( g_hScreen[g_nScreenIndex], ' ', 80*25, Coor, &dw );

}

 

void ScreenRelease()

{

     CloseHandle( g_hScreen[0] );

     CloseHandle( g_hScreen[1] );

}

 

void ScreenPrint( int x, int y, char *string )

{

     DWORD dw;

     COORD CursorPosition = { x, y };

     SetConsoleCursorPosition( g_hScreen[g_nScreenIndex], CursorPosition );    

     WriteFile( g_hScreen[g_nScreenIndex], string, strlen( string ), &dw, NULL );

}

// 1 ~ 15 까지 색상 설정 가능

void SetColor( unsigned short color )

{               

     SetConsoleTextAttribute( g_hScreen[g_nScreenIndex], color );

}

 

[소스 3-11] Screen.c

 

아래 소스는 main() 함수가 있는 파일의 소스이다.

 

 

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

#include <stdio.h>

#include "Screen.h"

 

void Init()

{

}

 

void Update()

{

}

 

void Render()

{

     ScreenClear();

     // 출력 코드

 

     ScreenFlipping();

}

 

void Release()

{

}

 

int main(void)

{

     ScreenInit();

     Init();        // 초기화

         

     while( 1 )

     {

         Update();    // 데이터 갱신

         Render();    // 화면 출력

     }

     

     Release();   // 해제

     ScreenRelease();

     return 0;

}

 

[소스 3-12] main() 함수가 있는 소스

 

결론적으로 앞으로 제작하게 되는 모든 게임에 [소스 3-12]의 기본 구조와 Screen.h와 Screen.c 내용이 공통적으로 적용되며 이 모든 것을 프레임워크라고 한다.

실제 게임에 사용되는 프레임워크는 보다 더 많은 함수와 에러 처리까지 들어 있는 경우가 대부분이다.

 

 


08 Term Projects

 

프레임워크 환경에서 1초에 화면 전환이 몇 번 일어나는가를 화면에 출력해주는 프로그램을 작성하여 보자.

저자는 윈도우7 환경에서 CPU는 i3 M350 2.27GHz이며 RAM은 4G, 그리고 비디오 카드는 ATI HD 5650 에서 실행했을 때 아래와 같이 출력되었다.

 

[그림 3-13] FPS 출력

 

 좋은하루강의가 도움이 되셨습니까? 손가락 꾸욱 눌러주는 센스 ~~ 

[출처] https://nowcampus.tistory.com/entry/3%EC%9E%A5?category=655340

 

 

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
번호 제목 글쓴이 날짜 조회 수
108 [Xamarin] Xamarin.Forms. Android 실행/ 디버깅시에 에뮬리이터 배포오류 Why am I getting this error in Xamarin.Forms using Visual Studio? file 졸리운_곰 2021.12.01 22
107 [Xamarin] Visual Studio 2019 를 설치하고 Xmarin.forms 빌드시 에러 : I am just download and start Visual Studio (Xamarin Project). But there is an ERROR NU1101. file 졸리운_곰 2021.12.01 53
106 [게임개발] How to Create Smarter NPCs in Games file 졸리운_곰 2021.08.31 23
105 (CGP)16장. 탱크 게임 file 졸리운_곰 2021.06.28 802
104 (CGP) 15장. 탱크 맵툴 만들기 file 졸리운_곰 2021.06.28 225
103 (CGP) 14장 Sogo 게임 file 졸리운_곰 2021.06.28 21
102 (CGP)13장. 패턴 뷰어 file 졸리운_곰 2021.06.28 19
101 (CGP) 12장 Snake 게임 file 졸리운_곰 2021.06.28 23
100 (CGP) 11장 Snake 게임 툴 만들기 file 졸리운_곰 2021.06.28 50
99 (CGP) 10장. 하트담기 게임 file 졸리운_곰 2021.06.28 25
98 (CGP) 9장. 하트 툴 만들기 file 졸리운_곰 2021.06.28 22
97 (CGP) 8장. 벽돌깨기 게임 file 졸리운_곰 2021.06.28 48
96 (CGP)7장. 짝 맞추기 게임 file 졸리운_곰 2021.06.28 131
95 (CGP) 6장. 두더지 잡기 게임 file 졸리운_곰 2021.06.28 113
94 (CGP) 5장. 슛골인 게임 file 졸리운_곰 2021.06.27 151
93 (CGP) 4장. 사운드 file 졸리운_곰 2021.06.27 78
» (CGP) 3장. 게임의 기본 구조 file 졸리운_곰 2021.06.27 126
91 (CGP) 2장. 함수 file 졸리운_곰 2021.06.27 36
90 (CGP) 1장. C언어 file 졸리운_곰 2021.06.27 60
89 (CGP) 0장. C를 이용한 게임프로그래밍 강좌를 시작하기 전에 file 졸리운_곰 2021.06.27 173
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED