home lecture link bbs blame

◈Core Library◈




단기간에 걸쳐 몇몇 소수의 인원이 모든 장르와 개발 플랫폼에 보편적인 게임 엔진을 만들기란
무척이나 어려운 일이다. 여기서는 이런 보편적인 엔진을 지향하지만 최소한 2D게임에서 활용될 수
있는 정도의 엔진을 만드는 것을 목표로 한다.


1 게임에서 사용되는 기본 객체들

게임엔진을 구현하는 방법은 크게 Top-down방식과, Bottom-up방식이 있는데 Top-down방식을
기본으로 하고, Bottom-up방식을 부분적으로 이용하는 것이 좋다.
먼저 게임엔진에 대한 코어(모듈, 패키지)를 만들기 위해서 과연 게임에서 사용하는 객체들이
무엇인지 살펴볼 필요가 있다.
게임의 객체는 크게 기본 객체, 확장개체 그리고 게임 응용 프로그램에서 필요로하고 정의된
응용객체 3부분으로 나눌 수있다.

기본객체라함은 게임이 실행되는 데 꼭 필요한 객체들로서 게임의 장르와 게임이 실행되는
플랫폼에 어느 정도 독립되면서 공통으로 사용되는 객체들을 말한다.
예를 들어, 키보드, 마우스에 대한 인풋객체, 2D, 3D 이미지에 대한 텍스춰 객체,
문자열 출력에 대한 폰트객체, 사운드 객체, 네트워크 객체, 디바이스 객체 등등을 들 수 있다.

확장객체는 기본객체를 확장시켜 만든 객체들, 예를 들어 지형 객체, 유저인터페이스 객체,
이펙트 객체, 모델객체, 카메라 객체 등등이 있고,
기본객체에는 포함되지 않지만 게임을 좀 더 재미 있게 만들 수 있도록 보조적인 역할을 하는
객체들로 예를 들어, 한글 입력처리에 대한 IME 객체, 인공지능 객체 등등으로 구성되어 있다.

응용객체는 게임엔진에는 포함되지 않지만 게임응용프로그램에서 자체적으로 필요로 하는
객체들이다. 이 객체들은 메인 프로그래머의 손에 의해 만들어 지며, 후보Core부분에 올려
놓았다가 보편성이 존재하면 엔진의 코어에 제2 확장객체로 포함될 수 있는 객체들이다.

지금까지의 내용을 UML로 본다면 객체에 대한 설계는 구조적인 행위에 해당하는 것이다.

자 이제 기본 객체들을 구현해 보자. 구현할 기본객체는 인풋, 폰트, 텍스춰, 사운드, 그리고
디바이스 객체들로 한정하겠다.


1.1 기본객체에 대한 인터페이스 구현

객체에 대한 특징, 상태, 행위 등을 먼저 생각하고 구현할 수 있지만 앞서도 이야기 했듯이
이러한 Bottom-up방식보다는 먼저 필요한 객체들을 만들어 놓고 구체적인 사항을 정리하는
Top-down방식이 기본객체를 만드는 일에도 도움이 된다.
Top-down방식은 그림으로 따지면 캔버스나 도화지에 연필로 그리는 밑그림이라 할 수 있다.
이러한 밑그림에 해당하는 기초적인 프로그램을 만들어 보자. 프로그램의 순서는
객체의 인터페이스--> 객체클래스-->파생클래스순으로 한다.
아래 프로그램은 앞의 interface장에서 가져왔다.

eg00.zip

기초적인 프로그램이 준비 되었으면 이제 기본객체들을 만들 차례다.
인풋, 폰트, 텍스춰, 사운드, 그리고 디바이스에 대한 객체들의 클래스를 먼저 만들고
객체의 구현에서 구체적으로 내용을 보강할 것이다. 잊지 말아야 할 것은 아무리 구체적인
코드가 보이고 이를 만들고 싶더라도 마음 한 구석에 자신의 욕망을 잠시 저장해 두자.

eg01_basic.zip


1.2 인스턴스에 대한 매니저

인스턴스에 대한 매니저는 최적화와 관련이 있는 내용이다. 만약 게임에서 A라는 모델에
쓰이는 텍스춰와 B라느 모델에 쓰이는 텍스춰가 동일할 경우 매번 모델이 생성될 때만다
이 텍스춰를 만들면 공간적인 메모리 낭비는 물론 텍스춰를 생성하고, 소멸하는
시간적인 낭비까지 초래한다. 자연히 표현하고 싶은 내용은 몇 발작 못 내딛게 된다.
결국 같은 텍스춰라면 이전에 만들어져 있다면 이의 주소를 리턴하고 없으면 새로 만들는
루틴이 필요로 하게된다. 또한 이전에 만들어져 있는지 아닌지를 확인하기 위해
'찾기 알고리듬Search Algorith'이 필요로 하게 된다. 이는 텍스춰를 관리하는 매니저를
꼬옥 두어야 해결이 된다는 것이다. 이러한 것은 텍스춰 뿐만아니라 쓰리뒤 모델,
폰트, 사운드, 데이터, 캐릭터, 사용자 등에서도 자주 나타난다.

그렇다면 어느 경우에 매니저를 두어야 하는가?

간단히 말한다면 Source의 수: instance의 수 = n: m (단, n≠1, m≠1) 이면 된다.
앞서 eg basic에 포함된 인스턴스중에서 디바이스와 인풋을 제외한 폰트, 텍스춰,
사운드 등은 매니저를 만들어야 하고, 인스턴스의 생성도 매니저를 통해서 만들어야
프로그램의 모양새가 좋아진다. 또한 매니저는 객체와 밀접하게 연결되어 있으므로
구체적인 코드 구현은 객체를 구현하면서 만들어야 한다.

eg02_manager.zip



2. 객체 구현


2.1 디바이스 객체(D3D9을 중심으로)

디바이스 객체란 무엇인가?
간단히 정리 한다면 디바이스객체란 그래픽 카드 추상 객체라고 할 수 있다. 현재
DirectX, OpenGL 등이 게임에서 가장 많이 사용되는 그래픽 라이브러리다. 이들에 대한
간단한 인터페이스를 만들어보자.

eg21_device1.zip


이렇게 단순한 인터페이스를 만들고, 확장시키면 좋을 것 같지만 현실은 다르다. 특히
Direct3D를 기본으로하는 디바이스 객체를 만들기 위해서 단순하게 멤버나 함수만을
생각하면 이 라이브러리를 가지고 게임을 만들게 되면 어려움이 생기는데 이러한 이유는
윈도우모드, 풀모드 전환, 풀모드에서의 윈도우 이벤트 발생에 따른 처리를 해야만
게임프로그램이 정상적으로 작동하기 때문이다. 그렇다면 이것까지 메인프로그래머가
게임을 제작하는 과정에서 처리를 해야하는지, 아니면 엔진에서 처리를 해야하는지
고민하게 되는데 후자를 추천하고 싶다. 전자를 선택하면 엔진은 D3D9만을 만들기만
하게되어 그리 어려움이 없다. 하지만 후자를 선택하게되면 전체적인 게임의 구조까지
고려를 해서 엔진을 만들어야 한다.

그렇다면 게임구조의 어느 부분을 염두해 두어야 하는가? 간단히 말한다면 게임이 실행
되는 위상이라고 할 수 있다. 게임의 위상은
초기화 및 시작(Init)--> 데이터 업데이트(FrameMove) --> 렌더링(Render) --> 소멸(Destroy)
이다. 또한 여기에 윈도우 모드의 변환에 따른 재설정(Restore), 무효화(Invalidate)가
포함이 된다.
이 부분을 게임엔진에서 융통성있게 포함시키기 위해서 함수 포인터가 필요하다.
다음은 함수 포인터를 활용한 엔진과 게임응용 프로그램에 대한 예제이다.

eg21_device2.zip


함수 포인터를 게임을 실행시키기 위한 준비단계에 놓고, Create, Run함수(새로추가)만을
사용한 게임구조를 만들어보자. 이러한 구조는 간결하고 쉽기 때문에 가장 흔하게 사용되는
구조이다.

eg21_device3.zip


메인프로그래머가 클래스를 사용하는 경우를 보자. 두 가지를 생각할 수 있는데 하나는
CLnDevD3D9를 상속받는 경우이다. 이런 경우 함수 포인터를 사용할 필요 없이 바로 구현하면
되지만 그러한 경우 엔진의 부분도 많이 바뀌어야 할 것이다. 다른 하나는 메인클래스는
그냥 두고 여기에 함수 포인터를 메인클래스에 구현한 다음 이를 연결하는 방법이다.
이 방법은 메인 클래스의 함수에 별도의 함수 포인터용 static함수가 필요하고 메인클래스가
생성될 시점에 먼저 함수 포인터를 연결해야 하는 경우가 발생한다.
장점은 다른 엔진을 사용할 경우 메인 클래스의 필요한 부분만을 고치면 되기 때문에 융통성이
생기고 엔진에 좀더 독립적인 구조를 만들 수 있다.
여기서는 두 번째 방법을 이용해보자.

eg21_device4.zip


지금까지의 코드는 의사코드(psudo-code) 이다. 이제 윈도우를 D3D9(이하 D3D)을 생성해보자. D3D
를 만들기 위해서 반드시 윈도우 핸들이 필요하다. 즉 윈도우가 만들어지고나서 D3D를 만들 수
있기 때문이다. 윈도우 핸들은 내부적으로 처리하고, 외부에서 윈도우를 만들 때 필요한 변수들은
윈도우의 위치, 크기, 윈도우 이름 정도만 있으면 된다. 이들 변수들은 구조체로 만들어서
생성함수에 전달할 수 있고, 아니면 문자열로 전달할 수 있다. 여기서는 구조체로 전달하는 방법을
이용하겠다.이 구조체 또한 엔진에서 만들어 주어야 한다.

eg21_device5.zip


디바이스가 잘 작동하는지 그림을 하나 띄워 보자. 편리하게 사용하기 위해 스프라이트를 얻는
LnDev_GetD3Sprite()함수를 추가했다.

eg21_device6.zip


지금까지 디바이스에 대해서 인터페이스를 게임 프로그램에서 응용되는 사례를 살펴보았다. 하지만
위의 코드들은 아직까지 게임프로그램에서 활용할 만한 쓰임새 있는 객체는 아니다. 많은 부분이
생략되어 있고, 이것은 앞으로 보강해야할 것이다. 이것은 점차 다른 객체들을 구현해 가면서 보강해
보도록 한다. 가장 쉬운 방법은 DX의 Wizard Code를 이용하는 것이다. 위저드 코드는 DX8.0부터
꾸준히 업데이트되어 상용화 게임에서도 충분히 사용할 수 있을 만큼 잘 짜여져 있다. 이 코드를
가지고 디바이스의 부족한 부분들을 채워보자.

eg21_device7.zip


2.2 텍스춰 객체

텍스춰 객체는 텍스춰 매니저를 통해서 생성하는 방법, 매니저를 이용하지 않는 방법 두가지를
고려해야 한다. 매니저를 이용하지 않는 방법은 함수를 사용할 것이다.

eg22_texture1.zip


다음은 매니저를 통해서 만드는 방법이다. 매니저는 텍스퉈 객체들의 인스턴스를 std::map으로 관리한다.
매니저에서 중요한 기능은 찾기와 찾기했을 때 없으면 생성하는 루틴 2개이다.
이것을 구현해 보자.

eg22_texture2.zip


2.3 인풋 객체

인풋객체는 키보드, 마우스 두 가지에만 한정해서 만들어보자. 인풋객체는 Window API함수를 이용하는 방법
과 Direct Input을 사용하는 방법 두 가지가 있는데 각각의 장단점이 존재한다. Window API를 사용할 경우
가상키를 그대로 사용하므로 호스트 프로그램에서 스크립트를 사용할 때에 키를 일치시키기 위한 과정을 거치지
않아도 된다. 예를 들어 int a=65 , 'A' VK_A 는 같은 값이다. 하지만 마우스에 대한 문제가 있는데
Window API함수에서는 휠 마우스용 처리함수가 없다. 휠마우스 처리를 하려면 윈도우 메시지를 이용하는
수밖에 없다. 윈도우 메시지를 이용하려면 함수 포인터를 메시지 처리부분 연결하든가 아니면 메시지
처리 함수에서 휠에 대한 이벤트와 값을 저장하고 이를 가져오는 방법을 만들어야 한다.
Direct Input은 자체로 휠 이벤트를 처리하기 때문에 Window API를 사용하는 것 보다 번거로움이 없지만 가상키
값과 Direct Input에서 설정한 키 값이 일치 하지 않아 키 맵핑을 해주어야 한다.
이 두가지 경우를 고려해서 인풋객체를 만들어보자.

먼저 Window API함수를 이용하는 경우다.

eg23_input_winapi.zip


다음으로 Direct Input을 이용하는 경우다.

eg23_input_direct.zip


2.4 폰트 객체


2.5 미디어 객체

Copyright (c) 2004 3dapi.com All rights reserved.

Creative Commons License