안녕하세요. 이번에 새로 3일자와 18일에 연재글을 맞게된 디퍼드H입니다.
간단한 제 소개를 해드린다면 현재 디지펜-계명대 복수학위 이수하면서 현재 미국에서 공부하고 졸업을 앞둔 학생입니다. 학교 커리큘럼에 맞추어 GAM프로젝트 (GAM100, GAM150(텍스트 게임), GAM200, GAM250(1년 2D 게임 프로젝트), GAM300, GAM350(1년 3D 프로젝트)) 겪었던 여러경험들을 여러분들과 공유해보고자 합니다.
이번에 제가 연재할 첫번째 내용은 '자기만의 게임엔진을 만들어보기' 입니다. 사실 여러분들 중 게임 프로그래머를 지망하는 학생분들도 많을거라고 생각됩니다. 전문적인 내용 예로 들어 저같은경우 그래픽(렌더링) 프로그래밍 지망생으로 디퍼드 셰이딩이라던가 여러 전문적인 렌더링 테크닉을 다루기 보다는 우선 게임을 쉽게 만들수 있도록 도와줄 연재글을 작성해보려고 합니다. (이부분에 대해서는 포프님이나 다른분들께서 다루어주실거라고 믿어 의심치 않습니다.)
'자기만의 게임엔진을 만들기부분에서 연재글이 몇화나 될지는 잘 모르겠지만 빨리빨리 넘어가기 보다는 프로그래밍을 막 시작하는 분들을 위한 초점으로 천천히 진행해보고자 합니다. 허나 혹시 여러분들중에서 C/C++에 대해서 전혀 모르신다면 미리 공부하고 저의 글을 보시는게 도움이 되실거라고 생각합니다. 저는 여러분들이 이미 C/C++언어에 대해서 잘아신다는 전제하에 진행하도록 하겠습니다.
자, 그럼 이제 오늘의 강의를 시작하도록하죠. 보통 게임엔진하면 대표적으로 언리얼 엔진, 크라이 엔진, 하복 엔진, 떠오르는 유니티 엔진등이 있습니다. '우와, 이런 게임엔진을 어떻게 만들어?' 물론 우리가 만들어나갈 게임 엔진은 이런 상용엔진은 아닙니다. 허나, 우리만의 게임엔진을 만들어 나가면서, 여러분들의 게임 프로그래밍에 대한 지식을 한층 더 높여줄거라고 확신합니다. 사실 프로그래밍 실력을 확실하고 빠르게 늘릴 수 있는 방법은 직접 프로그래밍을 많이 해보는 길입니다. 여러분들이 새로운 기술 또는 테크닉을 익혔을 경우 여러분들이 만든 엔진에서 구현을 해본다면 크게 도움이 될 것입니다.
자자, 여러분들만의 게임 엔진 구현의 장점 나열은 여기까지하고 이제 여러분이 만드실 게임엔진에 대해 전체적 구조를 알아보도록 하죠. 아래의 그림을 보도록 합시다. 우리가 만들어갈 게임 엔진을 기본구조를 잘 표현하고 있는 그림입니다.
우선 그림 보면 CoreEngine 클래스가 보입니다. 이 클래스 우리가 만들 게임엔진에 핵심이 될 부분입니다. 이 클래스는 시스템과 관련된 메소드와 게임 루프 함수를 들고 있습니다. 게임 루프 함수는 게임 엔진에서 메인 심장이라고 보면되는데, 게임 루프는 CoreEngine 클래스에 속해있는 시스템들은 주기적으로 업데이트 시켜주는 메소드라고 생각하시면 됩니다. 자세한 내용은 후일 CoreEngine을 다룰때 알아보도록 합시다. CoreEngine 클래스 안에는 여러 시스템을 가지고 있는 컨테이너를 가지고 있는데, 그럼 시스템에는 무엇이 있겠는가 살펴보죠.
그림에서 보면 가장 처음으로 윈도우 시스템이 보입니다. 우선 윈도우 시스템에 대해서 간단히 설명하자면 윈도우 운영체제와 관련된 윈도우 창 만들기, 윈도우 메세지 제어등을 관리할 시스템이라고 생각하면 됩니다.
그 다음 시스템은 그래픽 시스템을 보도록 하죠. 그림을 보면, 어~! 그래픽 시스템에서는 뭔가 화살표 방향으로 Transform, Model, 또 Light로 연결이 되어있네요? 막 복잡하고 그렇게 보이지만, 간단히 말하자면 그냥 컴포넌트 클래스 입니다. 여기서 컴포넌트 클래스가 무엇인가 궁금해 하는 분들이 있을거라고 생각합니다. 잠깐 여기서 간단히 살펴보고 가봅시다. 아래의 그림을 봅시다.
그림에서 보면 GameObject 클래스가 있습니다. 이 게임 오브젝트 클래스는 우리가 흔히 게임상에서 말하는 적, 플레이어, 나무, 장애물, 추상적으로는 빛까지 이르는 오브젝트라고 말할 수 있습니다. 게임 오브젝트는 컴포넌트 컨테이너를 가지고 있는데, IComponent 클래스에서 상속받는 컴포넌트들로 구성이 되어있습니다. 사실 이 컴포넌트를 가장 쉽게 설명한다면 오브젝트들이 가지고 있는 유니크한 특징들을 객체 또는 클래스를 표현 했다고 생각한다는 것이 쉬울것 같습니다. 예로 들어 빛을 게임 오브젝트로 표현하고 싶다라고 한다면, 게임 오브젝트 클래스에 Light라는 컴포넌트를 가지고 있으면 빛 오브젝트를 정의할 수 있습니다. 한가지 더 해보도록 하죠. 만약 캐릭터 오브젝트를 표현하고 싶다라고 한다면, Transform과 Model 컴포넌트를 가지고 있다면 캐릭터 오브젝트를 만들 수 있죠. 이렇듯, 컴포넌트 구성요소에 의해 우리는 게임 오브젝트 종류를 설정할 수 있습니다. 여러 게임 오브젝트를 추가시킬때 우린 굳이 클래스를 따로 정의 할 필요가 없게 되는거죠. 이외 컴포넌트를 이용한 게임 오브젝트를 만들면서 또다른 여러가지 이점이 있지만 예로 들어 Data-Driven 구조로 Multi-threading 구현이 쉬워진다라는 등, 이외의 이점은 다음에 우리가 오브젝트와 컴포넌트를 실제로 구현할때 더 자세히 다루어 보도록 하겠습니다.
자, 다시 그래픽 시스템으로 돌아와서, 그래픽에서는 Light, Transform, 그리고 Model 컴포넌트가 연결되어 있습니다. 이게 무슨뜻일까요? 컴포넌트 기반의 게임엔진에서는 각 시스템에서 특정 컴포넌트들의 업데이트 담당하게 됩니다. 예로 들어 Transform 컴포넌트는 게임 오브젝트의 위치를 나타냅니다. 이 위치는 주로 그래픽 시스템에서 랜더링할때 담당하기때문에 그래픽에서 담당하게 됩니다. 빛 컴포넌트도 마찬가지입니다. 이 빛 컴포넌트는 빛 오브젝트에 속해질 컴포넌트로 오직 그래픽에서 랜더링할때 사용될거기 때문에 그래픽 시스템에 속해 질것입니다. 모델 컴포넌트로 마찬가지입니다. 정리해보면, 시스템들은 보통 1가지 이상의 컴포넌트 업데이트를 담당하게 됩니다. 또 다른 예를 들게 되면 여러분들이 쉽게 이해 되실겁니다. 자, RigidBody 컴포넌트가 있다고 가정합시다. 여러분들 중에 물리 엔진을 한번이라도 만져 보셨거나 직접 만들어 보신 분이라면 이 컴포넌트는 물리에 관련이 있다는 것을 바로 직감하셨을 겁니다. 그렇습니다 그럼 이 컴포넌트는 물리 시스템에서 관리를 하게 될것입니다. 이제 감이 오시나요? 혹시 난 아직 이해가 안된다 하시는 분들이 있다면 걱정마세요. 지금은 '아 이런게 있구나.' 라는 정도만 알아도 충분합니다. 또 자세히 돌아볼 기회가 있으니깐요.
지금까지 좀 많은걸 배운것 같은데 잠시 정리하고 지나가 봅시다.
- CoreEngine이란?
- 게임 루프 ; 게임의 심장
- 시스템들을 초기화와 관리한다.
- 컴포넌트란?
- 게임 오브젝트의 특징을 클래스 또는 객체화한 것이다.
- 컴포넌트가 모여 하나의 게임 오브젝트를 구성하게 된다.
- 각 시스템들은 하나 이상의 컴포넌트를 관리하게 된다.
- Data-Driven ; Multi-threading 구현
마지막으로 살펴볼 내용은 메세지 시스템입니다. 메세지 시스템이 왜 필요할까요? 윈도우 API에 있는 메세지 시스템으로 도 충분하지 않을까? 물론, 충분하지만, 커스텀 메세지 시스템 도입은 여러가지 이점들을 주는데 우선 대표적으로 클래스 사이 포인터 참조 사용을 줄이므로써 클래스 간의 엉킴이 적어집니다. 사실 포인터 참조는 너무 많은 곳에서 쓰이게 된다면 클래스가 서로서로 거미줄처럼 엉키게 됩니다. 이럴경우 나중에 새로운 함수 또는 리팩토링할때 너무 고쳐야할 부분이 많아지게 됩니다. 우선 아래의 그림을 보면 무슨 내용인가 쉽게 이해 될것이라고 생각합니다.
첫번째 그림은 만약 우리가 메세지 시스템을 사용하지 않는다는 것을 가정했을때의 모습을 그림으로 그려보았습니다. 메세지 시스템을 사용하지 않는다면, 서로 다른 메소드를 가져올때 하는 수 없이 포인터 참조를 할 수 밖에 없어집니다. 그러면 첫번째 그림과 같이 시스템들은 서로 서로 거미줄처럼 엮어질 것입니다. 물론, 디자인 패턴중 퍼사드 패턴을 이용하게 된다면, 클래스 간의 의존성을 상당히 줄일 수 있긴 합니다. 두번째 그림에서는 우리가 만약 메세지 시스템을 사용했을때의 모습입니다. 만약 입력시스템(InputSystem)에서 어떤 키가 입력 되었다고 가정합시다. 입력시스템에서는 키가 입력되었다는 사실을 우선 메세지 시스템에 보내게 됩니다. 메세지 시스템에서는 그 메세지를 큐에 저장을 해놨다가 한 프레임 지나고 메세지 큐에 있는 메세지를 딜레이 시간을 비교하여 메세지를 알맞은 발신자에게로 보내게 됩니다. 첫번째 그림과 두번째 그림을 비교해보면, 여러분들은 메세지 시스템을 사용한 부분이 훨씬 상호 의존성이 적다고 알 수 있을것입니다. 이와 같은 이점이 있는 메세지 시스템을 우리가 만들 게임엔진에 구현할 것입니다.
지금까지 우리가 만들 게임엔진에 대해 핵심적인 내용을 다루어보았는데요. 다음 시간에는 본격적으로 게임 엔진을 만들기 시작해 볼 겁니다. 물론 여기에서 다루었던 내용은 게임엔진에 중요한 부분만 다루었고 이제 본 강의에서는 하나하나 세세히 다루면서 나갈 생각입니다. Precompile Header를 어떻게 만들고 실행시키는가 하는 부분도 진도를 나가면서 다루어 나갈 생각입니다. 오늘 했던 내용이 혹시 이해 안되는 부분이 있더라도 걱정하세요. '아, 이런걸 할거구나.' 정도만 아시면 충분합니다. 다음시간부터는 절대 이런 생각을 가지면 안되고 철저히 해야하구요.
혹시 오늘 강의에서 궁금했던 점은 댓글에 남겨주시면 답해 드릴게요. 이 긴 강의를 읽어 주셨던 분들 수고하셨습니다. 비록 허접한 강의가 여러분들에게 도움이 되었기를 바랍니다.