1장에서는 게임 엔진의 개념을 전반적으로 소개한다.
velog 독자를 엔지니어라 생각하고 그에 관련한 핵심 내용만 담기로 한다.
'Raph Koster' - "사용자에게 점진적으로 난이도가 증가하는 도전과제를 제시, 궁극적으로 그 과정을 학습, 숙달하는 상호작용 경험을 제공하는 것" (A Theory of Fun for Game Design)
컴퓨터과학 - "덜 엄격한 실시간 상호적 행위자 기반 컴퓨터 시뮬레이션"
우리는 게임을 컴퓨터과학적으로 접근한다.
위 용어를 나누어 살펴보자.
서로 다른 '행위자'들 간 상호작용을 의미한다.
행위자 - 탈것, 캐릭터, 물체등의 객체
시뮬레이션 - 시간 내 동적 변화를 모델화
게임의 가상 세계는 고정되어 있지 않다.
행위자들의 상호작용으로 세계는 변화한다.
시간 제약을 지키지 못해도 치명적인 결과가 발생하지 않는다는 뜻이다.
그렇다면 '엄격한 실시간 시스템'은 어떤 것이 있을까?
시간 제약을 준수하지 못했을 경우 사용자에게 부상, 사망 등에 이르게 할 수 있는 시스템을 떠올리면 된다.
가령, 항공 제어 시스템, 원자력 발전소 안전 시스템과 같은 것들.
유래 : 1990년대, id 소프트의 FPS게임, 'Doom'에서 유래함. 해당 게임 소프트웨어는 코어 시스템과 사용자에게 보이는 요소들이 철저히 구분되어 있음.
엔진이라는 개념은 하나의 게임으로 완전히 새로운 게임을 제작하는 모드(mod) 커뮤니티의 시발점이 됨.
저자 그레고리 아저씨는 어디까지가 엔진이고 어디까지가 게임인지 명쾌한 구분은 없지만, 한가지 조언을 했다.
만일 어떤 게임 소프트웨어가 하드 코딩을 통해 작성됐다면, 그 소프트웨어는 재사용하기 매우 어려울 것이다.
따라서, 게임 엔진은 '확장가능 소프트웨어'를 지칭할 때 사용하는 것이 좋다!
...라고 조언한다.
다른 소프트웨어와 마찬 가지로 엔진도 그 계층이 나뉘어져 있다.
상위 계층은 하위 계층에 의존하지만 그 반대는 아니다. (당연한 소리)
Low-Level 부터 하나씩 소개한다.
말 그대로 목표하는 PC나 콘솔 시스템을 뜻한다.
하드웨어 제조사에서 제공하는 저수준 소프트웨어를 의미한다. 주로 하드웨어를 관리하고 불필요한 요소를 정리하는 역할을 한다.
여러 응용 프로그램들의 자원을 관리하는 역할을 한다.
주로 PC에서 자주 사용되는데, 최신 콘솔은 다양한 앱을 지원하기 때문에 전용 OS가 구현되어 있는 편이다.
게임 엔진은 다양한 모듈들을 적극적으로 사용한다.
아래는 그 예시들이다.
타 플랫폼에서의 일관성있는 동작을 보장하기 위해 설계된 계층.
여러 플랫폼을 지원할 수 있도록 엔진의 다른 부분들이 하드웨어에 종속되지 않게하는 역할을 수행한다.
게임 엔진을 비롯한 타 Cpp 소프트웨어는 유용한 유틸리티들이 필요하다.
아래는 그 예시들이다.
Assertion - 에러를 체크하는 코드들, 보통 최종 버전에서는 제거된다.
메모리 관리 - 거의 모든 엔진에서는 메모리 할당 문제를 위해 전용 메모리 시스템을 구현한다.
수학 라이브러리 - 벡터, 행렬, 사원수, 삼각함수, 기하 연산 등의 게임 프로그래머가 사용하는 모든 연산을 제공한다.
외부 패키지에 전적으로 의존하는 것이 아니라면 기본적인 자료구조와 알고리즘을 처리하기 위한 도구가 필요하다.
일반적으로 대상 플랫폼에서 효율적인 메모리 사용과 최적 성능을 내기 위해 직접 작성한다.
엔진의 모든 자원과 여타 엔진 데이터에 접근하는 곳에 일관된 인터페이스를 제공하는 역할을 수행한다.
사실 렌더링 엔진은 게임 엔진의 꽃이다.
그만큼 복잡하고 광범위한 계층 중 하나인데, 렌더링 엔진은 다음과 같은 하위 계층으로 이루어져 있다.
렌더링 엔진에서 가장 기초적인 부분이다.
기하학적 기본 단위를 빠르게 그리고 다양한 방식을 지원하는 것에 중점을 둔다.
저수준 렌더러는 또 다음과 같은 하위 요소로 분류할 수 있다.
그래픽 디바이스 인터페이스 - 그래픽 SDK 차원에서 GPU를 찾아 초기화하고 세팅하는 역할을 수행한다.
기타 렌더러 구성 요소 - 메시, 선 리스트와 같은 상위 계층에 전달할 기하학적 기본 단위들을 빠르게 처리하는 역할을 수행한다.
저수준 렌더러는 뷰포트에 대한 추상 개념(카메라 월드 행렬, 3D 투영 매개변수 등)을 제공한다.
또한 '머터리얼 시스템', '동적 조명 시스템'을 통해 GPU와 셰이더의 상태를 관리한다.
머터리얼 : 하나의 기하학적 기본 단위를 그리는 데에 쓰이는 텍스처와 활성화된 디바이스 설정, 사용할 셰이더들의 상태를 나타내는 정보.
조명 : 기본 단위를 그릴 때 어떤 조명 계산 방식을 사용할지 나타내는 정보.
조명과 셰이딩은 복잡한 주제이므로 그래픽스 관련 서적을 더 참고할 것.
저수준 렌더러는 후면 컬링과 카메라 절두체 컬링을 제외하고 모든 기하학적 단위를 '일단' 그린다.
그렇기에 시야 결정을 통해 보이는 부분만 가려낼 계층이 필요한데, 이 계층이 그 역할을 담당한다.
게임 세계가 작다면 단순 절두체 컬링만으로 충분하지만, 그렇지 않다면 좀 더 고급적인 '공간 분할' 기술이 필요하다.
이는 '보일 가능성이 있는 집합'을 빨리 찾아내 렌더링 성능을 향상시키기 위함에 있다.
공간 분할은 이진 공간 분할 트리, 옥트리 등의 다양한 형태가 있다.
이를 '장면 그래프'라 부르기도 하지만, 후자는 일종의 자료구조이며 전자를 포함하는 개념은 아니다.
저수준 렌더러는 사용된 공간 분할 방식, 장면 그래프 종류에 영향을 받지 않는 것이 이상적이다.
이 경우 같은 기본 단위 인터페이스를 사용하면서도 '보일 가능성이 있는 집합'을 판단하는 시스템은 각 개발 팀이 원하는 것을 사용할 수 있다는 이점이 있다.
파티클(연기, 불, ...), 데칼(발자국, 총알 자국, ...), 그림자, 풀 스크린 후처리 효과와 같은 것들을 시각 효과라 칭한다.
일반적으로 '이펙트 시스템'을 따로 설계하여 다양한 시각 효과를 전달하는 경우가 많다.
이 중, 파티클과 데칼은 렌더링 엔진 내부에서 구분된 시스템으로 분리되어 직접 저수준 렌더러에 입력을 주는 경우가 일반적이다.
그 반대로 조명, 그림자는 렌더링 엔진 내부에서 처리된다.
풀 스크린 후처리 효과는 렌더러에 포함시켜 구현할 수도 있고, 아니면 렌더러의 결과물을 통해 따로 동작하는 별개 시스템으로도 제작할 수 있다.
대부분 게임은 3D 그래픽 외에 그 위에 덮히는 2D 그래픽(게임 메뉴, 인게임 UI, ...)도 사용한다.
이와 같은 2D 그래픽은 텍스처를 입힌 사각형을 직교 투영해 그리기도 하고, 3D 빌보드에 그린 후 항상 카메라를 향하게 구현하기도 한다.
이 계층에는 풀 모션 비디오 포함되어 미리 녹화된 비디오를 재생하는 역할을 수행한다.
마찬가지로 인게임 시네마틱도 이 계층과 관련이 있는데, 마치 영화처럼 순차적인 장면을 3D로 재생한다는 점에서 그렇다.
게임은 실시간 시스템이기 때문에 항상 프로파일링을 해야 한다.
또한 메모리 성능을 향상 시키기 위해 그에 맞는 분석 툴도 함께 사용되어야 한다.
시중에는 훌륭한 디버깅 툴들(VTune, Quantify, ...)이 있지만, 일반적으로 게임 엔진은 자체적으로 이를 구현한다.
요구사항은 다음과 같다.
코드를 수동으로 인스트루먼테이션하는 시스템
실시간으로 프로파일링 수치를 보여주는 기능
성능값을 다른 문서 파일로 추출하는 기능
엔진과 그 하부 시스템이 사용 중인 메모리의 양을 측정하는 기능
메모리 사용량, 그 최대값, 누수 통계 등을 언제든지 추출할 수 있는 기능
디버그 메세지를 어디서나 출력할 수 있고, 어떤 항목을 체크할지, 얼마나 자세히 출력할지 조정하는 기능
플레이를 녹화하고 재현하는 기능(구현이 어렵지만 디버그에서는 최고)
충돌 체크는 모든 게임에서 중요하다.
보통은 강체 역학(물리)과 관련이 되어 있으며 최근에는 직접 이를 구현하지 않고 외부 SDK(Havok, PhysX, ...)를 사용한다.
게임 내 움직이는 모든 생명은 반드시 애니메이션 시스템이 있어야 한다.
여러 애니메이션 방식이 있지만 주로 스켈레탈 애니메이션에 중점을 둔다.
스켈레탈 메시 렌더링은 계층적으로 렌더러와 애니메이션 시스템 사이에 걸쳐 있는데, 서로 긴밀하게 동작하지만 인터페이스는 분명하게 분리되어 있다.
애니메이션 시스템이 캐릭터의 모든 관절을 특정 포즈로 위치시키면 각 관절의 위치는 행렬의 집합으로 렌더링 엔진에 전해진다.
그 다음 렌더러는 각 정점에 대해 변환 행렬을 거쳐 최종 정점 위치를 계산한다.
이 과정을 '스키닝'이라고 한다.
'래그 돌'을 사용하는 경우, 애니메이션과 물리 시스템 계층간에서 이런 밀접 관계를 볼 수 있다.
물리 시스템은 래그 돌의 신체 부분들을 서로 연결된 강체 시스템으로 취급하여 위치와 방향을 결정한다.
이후, 애니메이션 시스템이 렌더링 엔진에서 캐릭터를 그릴 때 필요한 행렬들을 계산한다.
모든 게임은 사용자의 입력을 받는 장치가 필요하다.
어떤 때에는 (특히, 콘솔 조이스틱에서) 반대로 출력(진동과 같은)을 내보내야 하는 경우도 있기 때문에 이 부분을 플레이어 I/O라 부르기도 한다.
이 때문에 플랫폼마다 다른 하드웨어 정보를 상위 게임 컨트롤에서 분리시키는 것이 주요 고려 사항이 될 수 있다.
오디오의 중요성은 그래픽 못지 않다.
좋은 오디오 시스템 없이는 훌륭한 게임을 만들 수 없다.
타 엔진을 사용하면서 마저도 더 좋은 오디오 시스템을 구현해서 교체하는 경우가 많을만큼 말이다.
따라서 높은 품질의 오디오를 완성시키기 위해서도 많은 노력이 필요하다.
여러 플레이어와 게임을 즐기게 하는 게임이 많다.
다음은 그 종류를 분류한 것이다.
단일 스크린 멀티플레이어 - 두 개 이상의 휴먼 인터페이스 장치를 한 대의 게임에 연결하는 방식으로, 게이머들은 같은 공간에서 하나의 스크린을 보게 된다.
분할 화면 멀티플레이어 - 앞서 소개된 방식과 유사하지만 화면이 여러개로 분할된다. 이때 게이머는 하나의 스크린 속에서 자신의 화면을 보게 된다.
네트워크 멀티플레이어 - 여러 대의 하드웨어가 네트워크를 통해 연결된다. 단일 기계, 단일 사용자가 사용한다.
대규모 다중 사용자 - 수천 수만명의 게이머가 광대한 가상 세계에서 게임을 즐긴다. 보통 MMO 게임에서 이를 확인할 수 있고 여러 대의 서버가 구현된다.
멀티 플레이어 기능을 지원하는 게임은 엔진을 디자인하는데 큰 영향을 미치는데, 게임 월드 객체 모델, 렌더러 등이 모두 영향을 받는다.
때문에 이미 완성된 싱글플레이어 게임을 멀티플레이어 게임으로 전환하는 것은 어려운 일이다. 따라서 개발 시작 시, 멀티플레이어 기능을 미리 고려해야한다.
그 반대의 경우는 간단하다.
멀티플레이어의 특수한 형태로, 플레이어가 한 명 밖에 없는 구조로 싱글플레이를 구현하는 게임도 많다.
게임 플레이라는 용어는 게임 내에서 하는 행동, 규칙, 캐릭터 능력 등을 모두 아우르는 말이다.
플레이를 개발할 때는 엔진의 다른 부분을 구현하는데 사용된 프로그래밍 언어를 그대로 사용하는 경우도 있고, 더 추상적인 스크립트 언어를 사용하는 경우도 있다. 때로는 둘다 사용하기도 한다.
아래는 이 계층에서 사용되는 여러 주요 기능들이다.
게임 월드에 속하는 여러 구성물들은 대게 객체지향적으로 모델화하고, 이를 OOP 언어로 구현하는 것이 보통이다.
소프트웨어 객체 모델이라는 용어는 객체 지향적 소프트웨어를 개발하는 데에 쓰이는 언어의 기능, 정책 같은 것을 뜻한다.
다음과 같은 이슈들을 포함한다.
엔진 디자인이 객체지향적인가
어떤 언어를 사용할 것인가
클래스 구조는 어떤 형태(수직적, 수평적)를 띌 것인가
템플릿과 정책 기반 디자인을 사용할 것인가, 아니면 다형성을 사용할 것인가
객체에 접근할 때 어떤 방식을 사용할 것인가 (포인터, 스마트 포인터, 핸들)
서로 다른 객체 식별은 어떻게 할 것인가 (물리 주소, 이름, GUID)
객체의 수명은 어떻게 관리할 것인가
시간의 흐름에 따른 각 객체 상태는 어떻게 시뮬레이션 할 것인가
게임 내 객체들은 서로 소통해야한다.
단순히 메세지를 보내는 쪽에서 받는 쪽 객체 멤버 함수를 호출하는 방법도 있다.
이벤트 기반 방식에서는 보내는 쪽이 정보를 담은 작은 구조체(이벤트 또는 메세지)를 만든다. 이벤트를 전달할 때는 받는 쪽의 '이벤트 핸들러' 함수를 호출한다.
많은 게임 엔진에서 스크립트 언어를 사용한다.
이는 게임 규칙과 콘텐츠를 쉽고 빠르게 개발하기 위해서이다.
최근 인공지능 시스템에도 공통적인 패턴이 등장함에 따라 게임 엔진에 통합되는 추세이다.
게임 플레이 기반 시스템과 하부 저수준 엔진 요소들이 갖추어졌을때, 드디어 게임 플레이 프로그래머와 디자이너가 게임 기능을 구현할 수 있다.
이 계층은 특정 게임에 맞추어 필요한 시스템들을 포함하고 있다.
게임 엔진에는 막대한 데이터가 필요하다.
게임은 본질적으로 멀티미디어 프로그램이기에 다양한 자원(3D 메시, 애니메이션 데이터, ...)이 사용된다.
모든 자원은 아티스트가 만들고 다듬는데, 이를 디지털 콘텐츠 생성 도구라 한다.
디지털 콘텐츠 생성 도구로 제작된 데이터를 바로 게임 엔진에 사용할 수 없다.
이는 두가지 이유가 있다.
따라서 게임 엔진에 이를 사용하려면 접근성이 더 뛰어난 포맷으로 내보내야 한다.
성공적으로 내보냈다고 하더라도 게임 엔진에서 사용하기 위해 더 가공해야 한다.
게임에서 눈에 보이는 기하 형태는 크게 두가지로 나뉜다.
평면 여러개로 이루어진 볼록 입체 다각형의 모음이다.
보통 게임 데이터로 바로 만들고 작업할 수 있다.
장점은 빠르고 만들기 쉽고 게임 디자이너가 쉽게 사용할 수 있으며 충돌 볼륨의 역할도 수행할 수 있다.
단점은 복잡한 물체 구현이 어렵고 관절이 있거나 동적인 물체는 만들기 힙들다는 것이다.
볼록 입체 다각형과 반대로 복잡한 형태를 표현함에 있어서 우월하다.
메시란 삼각형과 정점으로 이루어진 모양을 가진 형태를 뜻하는데, 머터리얼이 하나 이상 연관된다.
스켈레탈 메시는 애니메이션을 위해 뼈대 구조에 연결되는 특수 형태의 메시이다. '스킨'이라 부르기도 하며 각 정점에는 뼈대의 어느 관절이 연결되어 있는지 정보가 담겨있다.
스켈레탈 메시를 그리기위해서는 세가지 데이터(메시, 뼈대 구조, 움직임을 나타내는 애니메이션)가 필요하다.
스켈레탈 애니메이션 원본은 43행렬 모음인데, 보통 100여개가 넘는 관절들을 초당 30번은 표현해야하기 때문에 용량이 엄청나다.
그래서 대부분은 이를 압축해서 저장한다.
압축방식은 엔진마다 다르며 저작권이 있는 경우도 있다.
게임에서 가장 널리 쓰이는 포맷은 웨이브 파일(.wav) 형식이다.
오디오 파일들은 관리 편이성이나 접근성 등의 기준에 따라 묶어서 저장된다.
아티스트가 제작한 파티클 효과는 대게 게임 엔진에서 소화하기 벅차다.
그래서 보통 엔진에서 처리할 만한 수준의 효과를 구현하는 도구를 만들어 사용한다.
게임 월드는 게임 엔진에서 다루는 모든 것이 모이는 곳이다.
훌륭한 월드 에디터를 만들기란 어렵지만 제대로 된 게임 엔진이라면 빠져서는 안되는 중요한 요소이다.
게임 엔진에서 툴이 자리하는 위치는 다양하다.
엔진의 하위 계층을 공유하기도 하고 아예 게임 엔진에 포함되는 경우도 있다.
그러나 너무 밀접하게 연결되어 있으면 생산성이 떨어진다.
여기까지 Jason Gregory 아저씨의
게임 엔진 아키텍처 2판 번역서 1장의 소개이다.
서적에는 더 다양한 예시와 실제 사례를 자세히 소개하므로 이 내용이 흥미로웠다면 정독하기를 추천한다 :)